Merge "Improve aarch64 MonitorEntry/Exit assembly code"
diff --git a/Android.mk b/Android.mk
index f3ab3c1..0e86188 100644
--- a/Android.mk
+++ b/Android.mk
@@ -42,7 +42,7 @@
.PHONY: clean-oat-host
clean-oat-host:
- find $(OUT_DIR) -name "*.oat" -o -name "*.odex" -o -name "*.art" | xargs rm -f
+ find $(OUT_DIR) -name "*.oat" -o -name "*.odex" -o -name "*.art" -o -name '*.vdex' | xargs rm -f
ifneq ($(TMPDIR),)
rm -rf $(TMPDIR)/$(USER)/test-*/dalvik-cache/*
rm -rf $(TMPDIR)/android-data/dalvik-cache/*
diff --git a/build/Android.bp b/build/Android.bp
index b1553c7..6c9f1d4 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -146,6 +146,7 @@
"external/valgrind",
"external/vixl/src",
"external/zlib",
+ "libnativehelper/platform_include"
],
tidy_checks: [
diff --git a/build/Android.common_test.mk b/build/Android.common_test.mk
index b7a2379..1591e34 100644
--- a/build/Android.common_test.mk
+++ b/build/Android.common_test.mk
@@ -66,9 +66,6 @@
# Do you want to test the optimizing compiler with graph coloring register allocation?
ART_TEST_OPTIMIZING_GRAPH_COLOR ?= $(ART_TEST_FULL)
-# Do we want to test a non-PIC-compiled core image?
-ART_TEST_NPIC_IMAGE ?= $(ART_TEST_FULL)
-
# Do we want to test PIC-compiled tests ("apps")?
ART_TEST_PIC_TEST ?= $(ART_TEST_FULL)
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index c319b1a..b661e00 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -123,16 +123,16 @@
ART_GTEST_elf_writer_test_TARGET_DEPS := $(TARGET_CORE_IMAGE_DEFAULT_64) $(TARGET_CORE_IMAGE_DEFAULT_32)
ART_GTEST_dex2oat_environment_tests_HOST_DEPS := \
- $(HOST_CORE_IMAGE_optimizing_pic_64) \
- $(HOST_CORE_IMAGE_optimizing_pic_32) \
- $(HOST_CORE_IMAGE_interpreter_pic_64) \
- $(HOST_CORE_IMAGE_interpreter_pic_32) \
+ $(HOST_CORE_IMAGE_optimizing_64) \
+ $(HOST_CORE_IMAGE_optimizing_32) \
+ $(HOST_CORE_IMAGE_interpreter_64) \
+ $(HOST_CORE_IMAGE_interpreter_32) \
$(HOST_OUT_EXECUTABLES)/patchoatd
ART_GTEST_dex2oat_environment_tests_TARGET_DEPS := \
- $(TARGET_CORE_IMAGE_optimizing_pic_64) \
- $(TARGET_CORE_IMAGE_optimizing_pic_32) \
- $(TARGET_CORE_IMAGE_interpreter_pic_64) \
- $(TARGET_CORE_IMAGE_interpreter_pic_32) \
+ $(TARGET_CORE_IMAGE_optimizing_64) \
+ $(TARGET_CORE_IMAGE_optimizing_32) \
+ $(TARGET_CORE_IMAGE_interpreter_64) \
+ $(TARGET_CORE_IMAGE_interpreter_32) \
$(TARGET_OUT_EXECUTABLES)/patchoatd
ART_GTEST_oat_file_assistant_test_HOST_DEPS := \
diff --git a/build/Android.oat.mk b/build/Android.oat.mk
index 0086660..f53740e 100644
--- a/build/Android.oat.mk
+++ b/build/Android.oat.mk
@@ -38,11 +38,10 @@
# Use dex2oat debug version for better error reporting
# $(1): compiler - optimizing, interpreter or interpreter-access-checks.
-# $(2): pic/no-pic
-# $(3): 2ND_ or undefined, 2ND_ for 32-bit host builds.
-# $(4): wrapper, e.g., valgrind.
-# $(5): dex2oat suffix, e.g, valgrind requires 32 right now.
-# $(6): multi-image.
+# $(2): 2ND_ or undefined, 2ND_ for 32-bit host builds.
+# $(3): wrapper, e.g., valgrind.
+# $(4): dex2oat suffix, e.g, valgrind requires 32 right now.
+# $(5): multi-image.
# NB depending on HOST_CORE_DEX_LOCATIONS so we are sure to have the dex files in frameworks for
# run-test --no-image
define create-core-oat-host-rules
@@ -50,7 +49,6 @@
core_image_name :=
core_oat_name :=
core_infix :=
- core_pic_infix :=
core_dex2oat_dependency := $(DEX2OAT_DEPENDENCY)
ifeq ($(1),optimizing)
@@ -70,19 +68,8 @@
$$(error found $(1) expected interpreter, interpreter-access-checks, or optimizing)
endif
- ifeq ($(2),pic)
- core_compile_options += --compile-pic
- endif
- ifeq ($(2),no-pic)
- core_pic_infix := -npic
- endif
- ifneq ($(filter-out pic no-pic,$(2)),)
- # Technically this test is not precise, but hopefully good enough.
- $$(error found $(2) expected pic or no-pic)
- endif
-
- # If $(6) is true, generate a multi-image.
- ifeq ($(6),true)
+ # If $(5) is true, generate a multi-image.
+ ifeq ($(5),true)
core_multi_infix := -multi
core_multi_param := --multi-image --no-inline-from=core-oj-hostdex.jar
core_multi_group := _multi
@@ -92,20 +79,20 @@
core_multi_group :=
endif
- core_image_name := $($(3)HOST_CORE_IMG_OUT_BASE)$$(core_infix)$$(core_pic_infix)$$(core_multi_infix)$(4)$(CORE_IMG_SUFFIX)
- core_oat_name := $($(3)HOST_CORE_OAT_OUT_BASE)$$(core_infix)$$(core_pic_infix)$$(core_multi_infix)$(4)$(CORE_OAT_SUFFIX)
+ core_image_name := $($(2)HOST_CORE_IMG_OUT_BASE)$$(core_infix)$$(core_multi_infix)$(3)$(CORE_IMG_SUFFIX)
+ core_oat_name := $($(2)HOST_CORE_OAT_OUT_BASE)$$(core_infix)$$(core_multi_infix)$(3)$(CORE_OAT_SUFFIX)
# Using the bitness suffix makes it easier to add as a dependency for the run-test mk.
- ifeq ($(3),)
- $(4)HOST_CORE_IMAGE_$(1)_$(2)$$(core_multi_group)_64 := $$(core_image_name)
+ ifeq ($(2),)
+ $(3)HOST_CORE_IMAGE_$(1)$$(core_multi_group)_64 := $$(core_image_name)
else
- $(4)HOST_CORE_IMAGE_$(1)_$(2)$$(core_multi_group)_32 := $$(core_image_name)
+ $(3)HOST_CORE_IMAGE_$(1)$$(core_multi_group)_32 := $$(core_image_name)
endif
- $(4)HOST_CORE_IMG_OUTS += $$(core_image_name)
- $(4)HOST_CORE_OAT_OUTS += $$(core_oat_name)
+ $(3)HOST_CORE_IMG_OUTS += $$(core_image_name)
+ $(3)HOST_CORE_OAT_OUTS += $$(core_oat_name)
# If we have a wrapper, make the target phony.
- ifneq ($(4),)
+ ifneq ($(3),)
.PHONY: $$(core_image_name)
endif
$$(core_image_name): PRIVATE_CORE_COMPILE_OPTIONS := $$(core_compile_options)
@@ -115,15 +102,15 @@
$$(core_image_name): $$(HOST_CORE_DEX_LOCATIONS) $$(core_dex2oat_dependency)
@echo "host dex2oat: $$@"
@mkdir -p $$(dir $$@)
- $$(hide) $(4) $$(DEX2OAT)$(5) --runtime-arg -Xms$(DEX2OAT_IMAGE_XMS) \
+ $$(hide) $(3) $$(DEX2OAT)$(4) --runtime-arg -Xms$(DEX2OAT_IMAGE_XMS) \
--runtime-arg -Xmx$(DEX2OAT_IMAGE_XMX) \
--image-classes=$$(PRELOADED_CLASSES) $$(addprefix --dex-file=,$$(HOST_CORE_DEX_FILES)) \
$$(addprefix --dex-location=,$$(HOST_CORE_DEX_LOCATIONS)) --oat-file=$$(PRIVATE_CORE_OAT_NAME) \
--oat-location=$$(PRIVATE_CORE_OAT_NAME) --image=$$(PRIVATE_CORE_IMG_NAME) \
- --base=$$(LIBART_IMG_HOST_BASE_ADDRESS) --instruction-set=$$($(3)ART_HOST_ARCH) \
- $$(LOCAL_$(3)DEX2OAT_HOST_INSTRUCTION_SET_FEATURES_OPTION) \
+ --base=$$(LIBART_IMG_HOST_BASE_ADDRESS) --instruction-set=$$($(2)ART_HOST_ARCH) \
+ $$(LOCAL_$(2)DEX2OAT_HOST_INSTRUCTION_SET_FEATURES_OPTION) \
--host --android-root=$$(HOST_OUT) --include-patch-information \
- --generate-debug-info --generate-build-id \
+ --generate-debug-info --generate-build-id --compile-pic \
$$(PRIVATE_CORE_MULTI_PARAM) $$(PRIVATE_CORE_COMPILE_OPTIONS)
$$(core_oat_name): $$(core_image_name)
@@ -134,7 +121,6 @@
core_image_name :=
core_oat_name :=
core_infix :=
- core_pic_infix :=
endef # create-core-oat-host-rules
# $(1): compiler - optimizing, interpreter or interpreter-access-checks.
@@ -142,12 +128,10 @@
# $(3): dex2oat suffix.
# $(4): multi-image.
define create-core-oat-host-rule-combination
- $(call create-core-oat-host-rules,$(1),no-pic,,$(2),$(3),$(4))
- $(call create-core-oat-host-rules,$(1),pic,,$(2),$(3),$(4))
+ $(call create-core-oat-host-rules,$(1),,$(2),$(3),$(4))
ifneq ($(HOST_PREFER_32_BIT),true)
- $(call create-core-oat-host-rules,$(1),no-pic,2ND_,$(2),$(3),$(4))
- $(call create-core-oat-host-rules,$(1),pic,2ND_,$(2),$(3),$(4))
+ $(call create-core-oat-host-rules,$(1),2ND_,$(2),$(3),$(4))
endif
endef
@@ -173,7 +157,6 @@
core_image_name :=
core_oat_name :=
core_infix :=
- core_pic_infix :=
core_dex2oat_dependency := $(DEX2OAT_DEPENDENCY)
ifeq ($(1),optimizing)
@@ -195,35 +178,24 @@
$$(error found $(1) expected interpreter, interpreter-access-checks, or optimizing)
endif
- ifeq ($(2),pic)
- core_compile_options += --compile-pic
- endif
- ifeq ($(2),no-pic)
- core_pic_infix := -npic
- endif
- ifneq ($(filter-out pic no-pic,$(2)),)
- #Technically this test is not precise, but hopefully good enough.
- $$(error found $(2) expected pic or no-pic)
- endif
-
- core_image_name := $($(3)TARGET_CORE_IMG_OUT_BASE)$$(core_infix)$$(core_pic_infix)$(4)$(CORE_IMG_SUFFIX)
- core_oat_name := $($(3)TARGET_CORE_OAT_OUT_BASE)$$(core_infix)$$(core_pic_infix)$(4)$(CORE_OAT_SUFFIX)
+ core_image_name := $($(2)TARGET_CORE_IMG_OUT_BASE)$$(core_infix)$(3)$(CORE_IMG_SUFFIX)
+ core_oat_name := $($(2)TARGET_CORE_OAT_OUT_BASE)$$(core_infix)$(3)$(CORE_OAT_SUFFIX)
# Using the bitness suffix makes it easier to add as a dependency for the run-test mk.
- ifeq ($(3),)
+ ifeq ($(2),)
ifdef TARGET_2ND_ARCH
- $(4)TARGET_CORE_IMAGE_$(1)_$(2)_64 := $$(core_image_name)
+ $(3)TARGET_CORE_IMAGE_$(1)_64 := $$(core_image_name)
else
- $(4)TARGET_CORE_IMAGE_$(1)_$(2)_32 := $$(core_image_name)
+ $(3)TARGET_CORE_IMAGE_$(1)_32 := $$(core_image_name)
endif
else
- $(4)TARGET_CORE_IMAGE_$(1)_$(2)_32 := $$(core_image_name)
+ $(3)TARGET_CORE_IMAGE_$(1)_32 := $$(core_image_name)
endif
- $(4)TARGET_CORE_IMG_OUTS += $$(core_image_name)
- $(4)TARGET_CORE_OAT_OUTS += $$(core_oat_name)
+ $(3)TARGET_CORE_IMG_OUTS += $$(core_image_name)
+ $(3)TARGET_CORE_OAT_OUTS += $$(core_oat_name)
# If we have a wrapper, make the target phony.
- ifneq ($(4),)
+ ifneq ($(3),)
.PHONY: $$(core_image_name)
endif
$$(core_image_name): PRIVATE_CORE_COMPILE_OPTIONS := $$(core_compile_options)
@@ -237,11 +209,11 @@
--image-classes=$$(PRELOADED_CLASSES) $$(addprefix --dex-file=,$$(TARGET_CORE_DEX_FILES)) \
$$(addprefix --dex-location=,$$(TARGET_CORE_DEX_LOCATIONS)) --oat-file=$$(PRIVATE_CORE_OAT_NAME) \
--oat-location=$$(PRIVATE_CORE_OAT_NAME) --image=$$(PRIVATE_CORE_IMG_NAME) \
- --base=$$(LIBART_IMG_TARGET_BASE_ADDRESS) --instruction-set=$$($(3)TARGET_ARCH) \
- --instruction-set-variant=$$($(3)DEX2OAT_TARGET_CPU_VARIANT) \
- --instruction-set-features=$$($(3)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) \
+ --base=$$(LIBART_IMG_TARGET_BASE_ADDRESS) --instruction-set=$$($(2)TARGET_ARCH) \
+ --instruction-set-variant=$$($(2)DEX2OAT_TARGET_CPU_VARIANT) \
+ --instruction-set-features=$$($(2)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) \
--android-root=$$(PRODUCT_OUT)/system --include-patch-information \
- --generate-debug-info --generate-build-id \
+ --generate-debug-info --generate-build-id --compile-pic \
$$(PRIVATE_CORE_COMPILE_OPTIONS) || (rm $$(PRIVATE_CORE_OAT_NAME); exit 1)
$$(core_oat_name): $$(core_image_name)
@@ -252,19 +224,16 @@
core_image_name :=
core_oat_name :=
core_infix :=
- core_pic_infix :=
endef # create-core-oat-target-rules
# $(1): compiler - optimizing, interpreter or interpreter-access-checks.
# $(2): wrapper.
# $(3): dex2oat suffix.
define create-core-oat-target-rule-combination
- $(call create-core-oat-target-rules,$(1),no-pic,,$(2),$(3))
- $(call create-core-oat-target-rules,$(1),pic,,$(2),$(3))
+ $(call create-core-oat-target-rules,$(1),,$(2),$(3))
ifdef TARGET_2ND_ARCH
- $(call create-core-oat-target-rules,$(1),no-pic,2ND_,$(2),$(3))
- $(call create-core-oat-target-rules,$(1),pic,2ND_,$(2),$(3))
+ $(call create-core-oat-target-rules,$(1),2ND_,$(2),$(3))
endif
endef
@@ -284,7 +253,7 @@
# Define a default core image that can be used for things like gtests that
# need some image to run, but don't otherwise care which image is used.
-HOST_CORE_IMAGE_DEFAULT_32 := $(HOST_CORE_IMAGE_optimizing_pic_32)
-HOST_CORE_IMAGE_DEFAULT_64 := $(HOST_CORE_IMAGE_optimizing_pic_64)
-TARGET_CORE_IMAGE_DEFAULT_32 := $(TARGET_CORE_IMAGE_optimizing_pic_32)
-TARGET_CORE_IMAGE_DEFAULT_64 := $(TARGET_CORE_IMAGE_optimizing_pic_64)
+HOST_CORE_IMAGE_DEFAULT_32 := $(HOST_CORE_IMAGE_optimizing_32)
+HOST_CORE_IMAGE_DEFAULT_64 := $(HOST_CORE_IMAGE_optimizing_64)
+TARGET_CORE_IMAGE_DEFAULT_32 := $(TARGET_CORE_IMAGE_optimizing_32)
+TARGET_CORE_IMAGE_DEFAULT_64 := $(TARGET_CORE_IMAGE_optimizing_64)
diff --git a/build/art.go b/build/art.go
index e7f7e21..053968d 100644
--- a/build/art.go
+++ b/build/art.go
@@ -70,10 +70,10 @@
"-DART_READ_BARRIER_TYPE_IS_"+barrierType+"=1")
}
- if envTrue(ctx, "ART_USE_VIXL_ARM_BACKEND") {
- // Used to enable the new VIXL-based ARM code generator.
- cflags = append(cflags, "-DART_USE_VIXL_ARM_BACKEND=1")
- asflags = append(asflags, "-DART_USE_VIXL_ARM_BACKEND=1")
+ if envTrue(ctx, "ART_USE_OLD_ARM_BACKEND") {
+ // Used to enable the old, pre-VIXL ARM code generator.
+ cflags = append(cflags, "-DART_USE_OLD_ARM_BACKEND=1")
+ asflags = append(asflags, "-DART_USE_OLD_ARM_BACKEND=1")
}
return cflags, asflags
diff --git a/compiler/Android.bp b/compiler/Android.bp
index f6a4db4..f5589cd 100644
--- a/compiler/Android.bp
+++ b/compiler/Android.bp
@@ -111,6 +111,7 @@
"optimizing/instruction_simplifier_shared.cc",
"optimizing/intrinsics_arm.cc",
"optimizing/intrinsics_arm_vixl.cc",
+ "optimizing/nodes_shared.cc",
"utils/arm/assembler_arm.cc",
"utils/arm/assembler_arm_vixl.cc",
"utils/arm/assembler_thumb2.cc",
@@ -127,7 +128,6 @@
"optimizing/scheduler_arm64.cc",
"optimizing/instruction_simplifier_arm64.cc",
"optimizing/intrinsics_arm64.cc",
- "optimizing/nodes_arm64.cc",
"utils/arm64/assembler_arm64.cc",
"utils/arm64/jni_macro_assembler_arm64.cc",
"utils/arm64/managed_register_arm64.cc",
diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc
index 2f9164c..d89cdba 100644
--- a/compiler/common_compiler_test.cc
+++ b/compiler/common_compiler_test.cc
@@ -175,6 +175,7 @@
InstructionSet isa,
size_t number_of_threads) {
compiler_options_->boot_image_ = true;
+ compiler_options_->SetCompilerFilter(GetCompilerFilter());
compiler_driver_.reset(new CompilerDriver(compiler_options_.get(),
verification_results_.get(),
kind,
diff --git a/compiler/common_compiler_test.h b/compiler/common_compiler_test.h
index 0d45a50..98dcf20 100644
--- a/compiler/common_compiler_test.h
+++ b/compiler/common_compiler_test.h
@@ -77,6 +77,10 @@
virtual ProfileCompilationInfo* GetProfileCompilationInfo();
+ virtual CompilerFilter::Filter GetCompilerFilter() const {
+ return CompilerFilter::kDefaultCompilerFilter;
+ }
+
virtual void TearDown();
void CompileClass(mirror::ClassLoader* class_loader, const char* class_name)
diff --git a/compiler/compiler.h b/compiler/compiler.h
index 2ca0b77..908d366 100644
--- a/compiler/compiler.h
+++ b/compiler/compiler.h
@@ -27,6 +27,7 @@
class JitCodeCache;
}
namespace mirror {
+ class ClassLoader;
class DexCache;
}
@@ -63,7 +64,7 @@
InvokeType invoke_type,
uint16_t class_def_idx,
uint32_t method_idx,
- jobject class_loader,
+ Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
Handle<mirror::DexCache> dex_cache) const = 0;
diff --git a/compiler/dex/dex_to_dex_compiler.cc b/compiler/dex/dex_to_dex_compiler.cc
index d4f6545..76aeaa5 100644
--- a/compiler/dex/dex_to_dex_compiler.cc
+++ b/compiler/dex/dex_to_dex_compiler.cc
@@ -284,16 +284,13 @@
}
uint32_t method_idx = is_range ? inst->VRegB_3rc() : inst->VRegB_35c();
ScopedObjectAccess soa(Thread::Current());
- StackHandleScope<1> hs(soa.Self());
- Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
- soa.Decode<mirror::ClassLoader>(unit_.GetClassLoader())));
ClassLinker* class_linker = unit_.GetClassLinker();
ArtMethod* resolved_method = class_linker->ResolveMethod<ClassLinker::kForceICCECheck>(
GetDexFile(),
method_idx,
unit_.GetDexCache(),
- class_loader,
+ unit_.GetClassLoader(),
/* referrer */ nullptr,
kVirtual);
@@ -330,7 +327,7 @@
InvokeType invoke_type ATTRIBUTE_UNUSED,
uint16_t class_def_idx,
uint32_t method_idx,
- jobject class_loader,
+ Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
DexToDexCompilationLevel dex_to_dex_compilation_level) {
DCHECK(driver != nullptr);
diff --git a/compiler/dex/dex_to_dex_compiler.h b/compiler/dex/dex_to_dex_compiler.h
index 0a00d45..00c596d 100644
--- a/compiler/dex/dex_to_dex_compiler.h
+++ b/compiler/dex/dex_to_dex_compiler.h
@@ -18,6 +18,7 @@
#define ART_COMPILER_DEX_DEX_TO_DEX_COMPILER_H_
#include "dex_file.h"
+#include "handle.h"
#include "invoke_type.h"
namespace art {
@@ -25,6 +26,10 @@
class CompiledMethod;
class CompilerDriver;
+namespace mirror {
+class ClassLoader;
+} // namespace mirror
+
namespace optimizer {
enum class DexToDexCompilationLevel {
@@ -40,7 +45,7 @@
InvokeType invoke_type,
uint16_t class_def_idx,
uint32_t method_idx,
- jobject class_loader,
+ Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
DexToDexCompilationLevel dex_to_dex_compilation_level);
diff --git a/compiler/driver/compiler_driver-inl.h b/compiler/driver/compiler_driver-inl.h
index f296851..5823306 100644
--- a/compiler/driver/compiler_driver-inl.h
+++ b/compiler/driver/compiler_driver-inl.h
@@ -31,17 +31,12 @@
namespace art {
-inline mirror::ClassLoader* CompilerDriver::GetClassLoader(const ScopedObjectAccess& soa,
- const DexCompilationUnit* mUnit) {
- return soa.Decode<mirror::ClassLoader>(mUnit->GetClassLoader()).Ptr();
-}
-
inline mirror::Class* CompilerDriver::ResolveClass(
const ScopedObjectAccess& soa, Handle<mirror::DexCache> dex_cache,
Handle<mirror::ClassLoader> class_loader, dex::TypeIndex cls_index,
const DexCompilationUnit* mUnit) {
DCHECK_EQ(dex_cache->GetDexFile(), mUnit->GetDexFile());
- DCHECK_EQ(class_loader.Get(), GetClassLoader(soa, mUnit));
+ DCHECK_EQ(class_loader.Get(), mUnit->GetClassLoader().Get());
mirror::Class* cls = mUnit->GetClassLinker()->ResolveType(
*mUnit->GetDexFile(), cls_index, dex_cache, class_loader);
DCHECK_EQ(cls == nullptr, soa.Self()->IsExceptionPending());
@@ -56,7 +51,7 @@
const ScopedObjectAccess& soa, Handle<mirror::DexCache> dex_cache,
Handle<mirror::ClassLoader> class_loader, const DexCompilationUnit* mUnit) {
DCHECK_EQ(dex_cache->GetDexFile(), mUnit->GetDexFile());
- DCHECK_EQ(class_loader.Get(), GetClassLoader(soa, mUnit));
+ DCHECK_EQ(class_loader.Get(), mUnit->GetClassLoader().Get());
const DexFile::MethodId& referrer_method_id =
mUnit->GetDexFile()->GetMethodId(mUnit->GetDexMethodIndex());
return ResolveClass(soa, dex_cache, class_loader, referrer_method_id.class_idx_, mUnit);
@@ -87,7 +82,7 @@
const ScopedObjectAccess& soa, Handle<mirror::DexCache> dex_cache,
Handle<mirror::ClassLoader> class_loader, const DexCompilationUnit* mUnit,
uint32_t field_idx, bool is_static) {
- DCHECK_EQ(class_loader.Get(), GetClassLoader(soa, mUnit));
+ DCHECK_EQ(class_loader.Get(), mUnit->GetClassLoader().Get());
return ResolveFieldWithDexFile(soa, dex_cache, class_loader, mUnit->GetDexFile(), field_idx,
is_static);
}
@@ -139,7 +134,7 @@
ScopedObjectAccess& soa, Handle<mirror::DexCache> dex_cache,
Handle<mirror::ClassLoader> class_loader, const DexCompilationUnit* mUnit,
uint32_t method_idx, InvokeType invoke_type, bool check_incompatible_class_change) {
- DCHECK_EQ(class_loader.Get(), GetClassLoader(soa, mUnit));
+ DCHECK_EQ(class_loader.Get(), mUnit->GetClassLoader().Get());
ArtMethod* resolved_method =
check_incompatible_class_change
? mUnit->GetClassLinker()->ResolveMethod<ClassLinker::kForceICCECheck>(
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index 26c0818..7e91453 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -580,7 +580,7 @@
InvokeType invoke_type,
uint16_t class_def_idx,
uint32_t method_idx,
- jobject class_loader,
+ Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
optimizer::DexToDexCompilationLevel dex_to_dex_compilation_level,
bool compilation_enabled,
@@ -621,9 +621,6 @@
// Look-up the ArtMethod associated with this code_item (if any)
// -- It is later used to lookup any [optimization] annotations for this method.
ScopedObjectAccess soa(self);
- StackHandleScope<1> hs(soa.Self());
- Handle<mirror::ClassLoader> class_loader_handle(hs.NewHandle(
- soa.Decode<mirror::ClassLoader>(class_loader)));
// TODO: Lookup annotation from DexFile directly without resolving method.
ArtMethod* method =
@@ -631,7 +628,7 @@
dex_file,
method_idx,
dex_cache,
- class_loader_handle,
+ class_loader,
/* referrer */ nullptr,
invoke_type);
@@ -678,9 +675,14 @@
if (compile) {
// NOTE: if compiler declines to compile this method, it will return null.
- compiled_method = driver->GetCompiler()->Compile(code_item, access_flags, invoke_type,
- class_def_idx, method_idx, class_loader,
- dex_file, dex_cache);
+ compiled_method = driver->GetCompiler()->Compile(code_item,
+ access_flags,
+ invoke_type,
+ class_def_idx,
+ method_idx,
+ class_loader,
+ dex_file,
+ dex_cache);
}
if (compiled_method == nullptr &&
dex_to_dex_compilation_level != optimizer::DexToDexCompilationLevel::kDontDexToDexCompile) {
@@ -727,12 +729,14 @@
uint32_t method_idx = method->GetDexMethodIndex();
uint32_t access_flags = method->GetAccessFlags();
InvokeType invoke_type = method->GetInvokeType();
- StackHandleScope<1> hs(self);
+ StackHandleScope<2> hs(self);
Handle<mirror::DexCache> dex_cache(hs.NewHandle(method->GetDexCache()));
+ Handle<mirror::ClassLoader> class_loader(
+ hs.NewHandle(method->GetDeclaringClass()->GetClassLoader()));
{
ScopedObjectAccessUnchecked soa(self);
ScopedLocalRef<jobject> local_class_loader(
- soa.Env(), soa.AddLocalReference<jobject>(method->GetDeclaringClass()->GetClassLoader()));
+ soa.Env(), soa.AddLocalReference<jobject>(class_loader.Get()));
jclass_loader = soa.Env()->NewGlobalRef(local_class_loader.get());
// Find the dex_file
dex_file = method->GetDexFile();
@@ -766,7 +770,7 @@
invoke_type,
class_def_idx,
method_idx,
- jclass_loader,
+ class_loader,
*dex_file,
dex_to_dex_compilation_level,
true,
@@ -792,7 +796,7 @@
invoke_type,
class_def_idx,
method_idx,
- jclass_loader,
+ class_loader,
*dex_file,
dex_to_dex_compilation_level,
true,
@@ -1050,11 +1054,16 @@
}
bool CompilerDriver::ShouldCompileBasedOnProfile(const MethodReference& method_ref) const {
- if (profile_compilation_info_ == nullptr) {
- // If we miss profile information it means that we don't do a profile guided compilation.
- // Return true, and let the other filters decide if the method should be compiled.
+ // Profile compilation info may be null if no profile is passed.
+ if (!CompilerFilter::DependsOnProfile(compiler_options_->GetCompilerFilter())) {
+ // Use the compiler filter instead of the presence of profile_compilation_info_ since
+ // we may want to have full speed compilation along with profile based layout optimizations.
return true;
}
+ // If we are using a profile filter but do not have a profile compilation info, compile nothing.
+ if (profile_compilation_info_ == nullptr) {
+ return false;
+ }
bool result = profile_compilation_info_->ContainsMethod(method_ref);
if (kDebugProfileGuidedCompilation) {
@@ -1067,22 +1076,30 @@
class ResolveCatchBlockExceptionsClassVisitor : public ClassVisitor {
public:
- explicit ResolveCatchBlockExceptionsClassVisitor(
- std::set<std::pair<dex::TypeIndex, const DexFile*>>& exceptions_to_resolve)
- : exceptions_to_resolve_(exceptions_to_resolve) {}
+ ResolveCatchBlockExceptionsClassVisitor() : classes_() {}
virtual bool operator()(ObjPtr<mirror::Class> c) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
- const auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
- for (auto& m : c->GetMethods(pointer_size)) {
- ResolveExceptionsForMethod(&m);
- }
+ classes_.push_back(c);
return true;
}
- private:
- void ResolveExceptionsForMethod(ArtMethod* method_handle)
+ void FindExceptionTypesToResolve(
+ std::set<std::pair<dex::TypeIndex, const DexFile*>>* exceptions_to_resolve)
REQUIRES_SHARED(Locks::mutator_lock_) {
- const DexFile::CodeItem* code_item = method_handle->GetCodeItem();
+ const auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
+ for (ObjPtr<mirror::Class> klass : classes_) {
+ for (ArtMethod& method : klass->GetMethods(pointer_size)) {
+ FindExceptionTypesToResolveForMethod(&method, exceptions_to_resolve);
+ }
+ }
+ }
+
+ private:
+ void FindExceptionTypesToResolveForMethod(
+ ArtMethod* method,
+ std::set<std::pair<dex::TypeIndex, const DexFile*>>* exceptions_to_resolve)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ const DexFile::CodeItem* code_item = method->GetCodeItem();
if (code_item == nullptr) {
return; // native or abstract method
}
@@ -1102,9 +1119,9 @@
dex::TypeIndex encoded_catch_handler_handlers_type_idx =
dex::TypeIndex(DecodeUnsignedLeb128(&encoded_catch_handler_list));
// Add to set of types to resolve if not already in the dex cache resolved types
- if (!method_handle->IsResolvedTypeIdx(encoded_catch_handler_handlers_type_idx)) {
- exceptions_to_resolve_.emplace(encoded_catch_handler_handlers_type_idx,
- method_handle->GetDexFile());
+ if (!method->IsResolvedTypeIdx(encoded_catch_handler_handlers_type_idx)) {
+ exceptions_to_resolve->emplace(encoded_catch_handler_handlers_type_idx,
+ method->GetDexFile());
}
// ignore address associated with catch handler
DecodeUnsignedLeb128(&encoded_catch_handler_list);
@@ -1116,7 +1133,7 @@
}
}
- std::set<std::pair<dex::TypeIndex, const DexFile*>>& exceptions_to_resolve_;
+ std::vector<ObjPtr<mirror::Class>> classes_;
};
class RecordImageClassesVisitor : public ClassVisitor {
@@ -1170,8 +1187,14 @@
hs.NewHandle(class_linker->FindSystemClass(self, "Ljava/lang/Throwable;")));
do {
unresolved_exception_types.clear();
- ResolveCatchBlockExceptionsClassVisitor visitor(unresolved_exception_types);
- class_linker->VisitClasses(&visitor);
+ {
+ // Thread suspension is not allowed while ResolveCatchBlockExceptionsClassVisitor
+ // is using a std::vector<ObjPtr<mirror::Class>>.
+ ScopedAssertNoThreadSuspension ants(__FUNCTION__);
+ ResolveCatchBlockExceptionsClassVisitor visitor;
+ class_linker->VisitClasses(&visitor);
+ visitor.FindExceptionTypesToResolve(&unresolved_exception_types);
+ }
for (const auto& exception_type : unresolved_exception_types) {
dex::TypeIndex exception_type_idx = exception_type.first;
const DexFile* dex_file = exception_type.second;
@@ -1422,19 +1445,14 @@
dex_to_dex_references_.back().GetMethodIndexes().SetBit(method_ref.dex_method_index);
}
-bool CompilerDriver::CanAccessTypeWithoutChecks(uint32_t referrer_idx,
- Handle<mirror::DexCache> dex_cache,
- dex::TypeIndex type_idx) {
- // Get type from dex cache assuming it was populated by the verifier
- mirror::Class* resolved_class = dex_cache->GetResolvedType(type_idx);
+bool CompilerDriver::CanAccessTypeWithoutChecks(ObjPtr<mirror::Class> referrer_class,
+ ObjPtr<mirror::Class> resolved_class) {
if (resolved_class == nullptr) {
stats_->TypeNeedsAccessCheck();
return false; // Unknown class needs access checks.
}
- const DexFile::MethodId& method_id = dex_cache->GetDexFile()->GetMethodId(referrer_idx);
bool is_accessible = resolved_class->IsPublic(); // Public classes are always accessible.
if (!is_accessible) {
- mirror::Class* referrer_class = dex_cache->GetResolvedType(method_id.class_idx_);
if (referrer_class == nullptr) {
stats_->TypeNeedsAccessCheck();
return false; // Incomplete referrer knowledge needs access check.
@@ -1451,12 +1469,9 @@
return is_accessible;
}
-bool CompilerDriver::CanAccessInstantiableTypeWithoutChecks(uint32_t referrer_idx,
- Handle<mirror::DexCache> dex_cache,
- dex::TypeIndex type_idx,
+bool CompilerDriver::CanAccessInstantiableTypeWithoutChecks(ObjPtr<mirror::Class> referrer_class,
+ ObjPtr<mirror::Class> resolved_class,
bool* finalizable) {
- // Get type from dex cache assuming it was populated by the verifier.
- mirror::Class* resolved_class = dex_cache->GetResolvedType(type_idx);
if (resolved_class == nullptr) {
stats_->TypeNeedsAccessCheck();
// Be conservative.
@@ -1464,10 +1479,8 @@
return false; // Unknown class needs access checks.
}
*finalizable = resolved_class->IsFinalizable();
- const DexFile::MethodId& method_id = dex_cache->GetDexFile()->GetMethodId(referrer_idx);
bool is_accessible = resolved_class->IsPublic(); // Public classes are always accessible.
if (!is_accessible) {
- mirror::Class* referrer_class = dex_cache->GetResolvedType(method_id.class_idx_);
if (referrer_class == nullptr) {
stats_->TypeNeedsAccessCheck();
return false; // Incomplete referrer knowledge needs access check.
@@ -1511,9 +1524,7 @@
mirror::Class* referrer_class;
Handle<mirror::DexCache> dex_cache(mUnit->GetDexCache());
{
- StackHandleScope<1> hs(soa.Self());
- Handle<mirror::ClassLoader> class_loader_handle(
- hs.NewHandle(soa.Decode<mirror::ClassLoader>(mUnit->GetClassLoader())));
+ Handle<mirror::ClassLoader> class_loader_handle = mUnit->GetClassLoader();
resolved_field = ResolveField(soa, dex_cache, class_loader_handle, mUnit, field_idx, false);
referrer_class = resolved_field != nullptr
? ResolveCompilingMethodsClass(soa, dex_cache, class_loader_handle, mUnit) : nullptr;
@@ -2585,10 +2596,18 @@
continue;
}
previous_direct_method_idx = method_idx;
- CompileMethod(soa.Self(), driver, it.GetMethodCodeItem(), it.GetMethodAccessFlags(),
- it.GetMethodInvokeType(class_def), class_def_index,
- method_idx, jclass_loader, dex_file, dex_to_dex_compilation_level,
- compilation_enabled, dex_cache);
+ CompileMethod(soa.Self(),
+ driver,
+ it.GetMethodCodeItem(),
+ it.GetMethodAccessFlags(),
+ it.GetMethodInvokeType(class_def),
+ class_def_index,
+ method_idx,
+ class_loader,
+ dex_file,
+ dex_to_dex_compilation_level,
+ compilation_enabled,
+ dex_cache);
it.Next();
}
// Compile virtual methods
@@ -2602,10 +2621,17 @@
continue;
}
previous_virtual_method_idx = method_idx;
- CompileMethod(soa.Self(), driver, it.GetMethodCodeItem(), it.GetMethodAccessFlags(),
- it.GetMethodInvokeType(class_def), class_def_index,
- method_idx, jclass_loader, dex_file, dex_to_dex_compilation_level,
- compilation_enabled, dex_cache);
+ CompileMethod(soa.Self(),
+ driver, it.GetMethodCodeItem(),
+ it.GetMethodAccessFlags(),
+ it.GetMethodInvokeType(class_def),
+ class_def_index,
+ method_idx,
+ class_loader,
+ dex_file,
+ dex_to_dex_compilation_level,
+ compilation_enabled,
+ dex_cache);
it.Next();
}
DCHECK(!it.HasNext());
diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h
index 5b4c751..1e5c43d 100644
--- a/compiler/driver/compiler_driver.h
+++ b/compiler/driver/compiler_driver.h
@@ -187,16 +187,14 @@
REQUIRES(!requires_constructor_barrier_lock_);
// Are runtime access checks necessary in the compiled code?
- bool CanAccessTypeWithoutChecks(uint32_t referrer_idx,
- Handle<mirror::DexCache> dex_cache,
- dex::TypeIndex type_idx)
+ bool CanAccessTypeWithoutChecks(ObjPtr<mirror::Class> referrer_class,
+ ObjPtr<mirror::Class> resolved_class)
REQUIRES_SHARED(Locks::mutator_lock_);
// Are runtime access and instantiable checks necessary in the code?
// out_is_finalizable is set to whether the type is finalizable.
- bool CanAccessInstantiableTypeWithoutChecks(uint32_t referrer_idx,
- Handle<mirror::DexCache> dex_cache,
- dex::TypeIndex type_idx,
+ bool CanAccessInstantiableTypeWithoutChecks(ObjPtr<mirror::Class> referrer_class,
+ ObjPtr<mirror::Class> resolved_class,
bool* out_is_finalizable)
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -370,10 +368,6 @@
uint32_t field_idx)
REQUIRES_SHARED(Locks::mutator_lock_);
- mirror::ClassLoader* GetClassLoader(const ScopedObjectAccess& soa,
- const DexCompilationUnit* mUnit)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
private:
void PreCompile(jobject class_loader,
const std::vector<const DexFile*>& dex_files,
diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc
index 1e4ca16..97954f3 100644
--- a/compiler/driver/compiler_driver_test.cc
+++ b/compiler/driver/compiler_driver_test.cc
@@ -101,6 +101,7 @@
};
// Disabled due to 10 second runtime on host
+// TODO: Update the test for hash-based dex cache arrays. Bug: 30627598
TEST_F(CompilerDriverTest, DISABLED_LARGE_CompileDexLibCore) {
CompileAll(nullptr);
@@ -246,6 +247,11 @@
return &profile_info_;
}
+ CompilerFilter::Filter GetCompilerFilter() const OVERRIDE {
+ // Use a profile based filter.
+ return CompilerFilter::kSpeedProfile;
+ }
+
std::unordered_set<std::string> GetExpectedMethodsForClass(const std::string& clazz) {
if (clazz == "Main") {
return std::unordered_set<std::string>({
@@ -304,7 +310,6 @@
// Need to enable dex-file writability. Methods rejected to be compiled will run through the
// dex-to-dex compiler.
- ProfileCompilationInfo info;
for (const DexFile* dex_file : GetDexFiles(class_loader)) {
ASSERT_TRUE(dex_file->EnableWrite());
}
diff --git a/compiler/driver/dex_compilation_unit.cc b/compiler/driver/dex_compilation_unit.cc
index 47b1929..7e8e812 100644
--- a/compiler/driver/dex_compilation_unit.cc
+++ b/compiler/driver/dex_compilation_unit.cc
@@ -21,7 +21,7 @@
namespace art {
-DexCompilationUnit::DexCompilationUnit(jobject class_loader,
+DexCompilationUnit::DexCompilationUnit(Handle<mirror::ClassLoader> class_loader,
ClassLinker* class_linker,
const DexFile& dex_file,
const DexFile::CodeItem* code_item,
diff --git a/compiler/driver/dex_compilation_unit.h b/compiler/driver/dex_compilation_unit.h
index 854927d..24a9a5b 100644
--- a/compiler/driver/dex_compilation_unit.h
+++ b/compiler/driver/dex_compilation_unit.h
@@ -34,7 +34,7 @@
class DexCompilationUnit : public DeletableArenaObject<kArenaAllocMisc> {
public:
- DexCompilationUnit(jobject class_loader,
+ DexCompilationUnit(Handle<mirror::ClassLoader> class_loader,
ClassLinker* class_linker,
const DexFile& dex_file,
const DexFile::CodeItem* code_item,
@@ -44,7 +44,7 @@
const VerifiedMethod* verified_method,
Handle<mirror::DexCache> dex_cache);
- jobject GetClassLoader() const {
+ Handle<mirror::ClassLoader> GetClassLoader() const {
return class_loader_;
}
@@ -113,7 +113,7 @@
}
private:
- const jobject class_loader_;
+ const Handle<mirror::ClassLoader> class_loader_;
ClassLinker* const class_linker_;
@@ -125,7 +125,7 @@
const uint32_t access_flags_;
const VerifiedMethod* verified_method_;
- Handle<mirror::DexCache> dex_cache_;
+ const Handle<mirror::DexCache> dex_cache_;
std::string symbol_;
};
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index d2dd30d..117d113 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -940,9 +940,11 @@
}
ObjPtr<mirror::DexCache> dex_cache = self->DecodeJObject(data.weak_root)->AsDexCache();
for (size_t i = 0; i < dex_cache->NumResolvedTypes(); i++) {
- Class* klass = dex_cache->GetResolvedType(dex::TypeIndex(i));
+ mirror::TypeDexCachePair pair =
+ dex_cache->GetResolvedTypes()[i].load(std::memory_order_relaxed);
+ mirror::Class* klass = pair.object.Read();
if (klass != nullptr && !KeepClass(klass)) {
- dex_cache->SetResolvedType(dex::TypeIndex(i), nullptr);
+ dex_cache->ClearResolvedType(dex::TypeIndex(pair.index));
}
}
ArtMethod** resolved_methods = dex_cache->GetResolvedMethods();
@@ -1922,8 +1924,7 @@
// above comment for intern tables.
ClassTable temp_class_table;
temp_class_table.ReadFromMemory(class_table_memory_ptr);
- CHECK_EQ(class_loaders_.size(), compile_app_image_ ? 1u : 0u);
- mirror::ClassLoader* class_loader = compile_app_image_ ? *class_loaders_.begin() : nullptr;
+ ObjPtr<mirror::ClassLoader> class_loader = GetClassLoader();
CHECK_EQ(temp_class_table.NumZygoteClasses(class_loader),
table->NumNonZygoteClasses(class_loader) + table->NumZygoteClasses(class_loader));
UnbufferedRootVisitor visitor(&root_visitor, RootInfo(kRootUnknown));
@@ -2213,7 +2214,7 @@
orig_dex_cache->FixupStrings(NativeCopyLocation(orig_strings, orig_dex_cache),
ImageAddressVisitor(this));
}
- GcRoot<mirror::Class>* orig_types = orig_dex_cache->GetResolvedTypes();
+ mirror::TypeDexCacheType* orig_types = orig_dex_cache->GetResolvedTypes();
if (orig_types != nullptr) {
copy_dex_cache->SetFieldPtrWithSize<false>(mirror::DexCache::ResolvedTypesOffset(),
NativeLocationInImage(orig_types),
diff --git a/compiler/image_writer.h b/compiler/image_writer.h
index cc7df1c..bdc7146 100644
--- a/compiler/image_writer.h
+++ b/compiler/image_writer.h
@@ -51,8 +51,13 @@
} // namespace space
} // namespace gc
+namespace mirror {
+class ClassLoader;
+} // namespace mirror
+
class ClassLoaderVisitor;
class ClassTable;
+class ImtConflictTable;
static constexpr int kInvalidFd = -1;
@@ -79,6 +84,11 @@
return true;
}
+ ObjPtr<mirror::ClassLoader> GetClassLoader() {
+ CHECK_EQ(class_loaders_.size(), compile_app_image_ ? 1u : 0u);
+ return compile_app_image_ ? *class_loaders_.begin() : nullptr;
+ }
+
template <typename T>
T* GetImageAddress(T* object) const REQUIRES_SHARED(Locks::mutator_lock_) {
if (object == nullptr || IsInBootImage(object)) {
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index 7c0cdbf..0ea1125 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -1060,6 +1060,7 @@
WriteCodeMethodVisitor(OatWriter* writer, OutputStream* out, const size_t file_offset,
size_t relative_offset) SHARED_LOCK_FUNCTION(Locks::mutator_lock_)
: OatDexMethodVisitor(writer, relative_offset),
+ class_loader_(writer->HasImage() ? writer->image_writer_->GetClassLoader() : nullptr),
out_(out),
file_offset_(file_offset),
soa_(Thread::Current()),
@@ -1245,6 +1246,7 @@
}
private:
+ ObjPtr<mirror::ClassLoader> class_loader_;
OutputStream* const out_;
const size_t file_offset_;
const ScopedObjectAccess soa_;
@@ -1303,10 +1305,12 @@
}
mirror::Class* GetTargetType(const LinkerPatch& patch) REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(writer_->HasImage());
ObjPtr<mirror::DexCache> dex_cache = GetDexCache(patch.TargetTypeDexFile());
- mirror::Class* type = dex_cache->GetResolvedType(patch.TargetTypeIndex());
+ ObjPtr<mirror::Class> type =
+ ClassLinker::LookupResolvedType(patch.TargetTypeIndex(), dex_cache, class_loader_);
CHECK(type != nullptr);
- return type;
+ return type.Ptr();
}
mirror::String* GetTargetString(const LinkerPatch& patch) REQUIRES_SHARED(Locks::mutator_lock_) {
diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h
index e4ad422..3a4c9db 100644
--- a/compiler/optimizing/builder.h
+++ b/compiler/optimizing/builder.h
@@ -54,7 +54,10 @@
compiler_driver_(driver),
compilation_stats_(compiler_stats),
block_builder_(graph, dex_file, code_item),
- ssa_builder_(graph, dex_compilation_unit->GetDexCache(), handles),
+ ssa_builder_(graph,
+ dex_compilation_unit->GetClassLoader(),
+ dex_compilation_unit->GetDexCache(),
+ handles),
instruction_builder_(graph,
&block_builder_,
&ssa_builder_,
@@ -80,10 +83,12 @@
code_item_(code_item),
dex_compilation_unit_(nullptr),
compiler_driver_(nullptr),
- null_dex_cache_(),
compilation_stats_(nullptr),
block_builder_(graph, nullptr, code_item),
- ssa_builder_(graph, null_dex_cache_, handles),
+ ssa_builder_(graph,
+ handles->NewHandle<mirror::ClassLoader>(nullptr),
+ handles->NewHandle<mirror::DexCache>(nullptr),
+ handles),
instruction_builder_(graph,
&block_builder_,
&ssa_builder_,
@@ -96,7 +101,7 @@
/* code_generator */ nullptr,
/* interpreter_metadata */ nullptr,
/* compiler_stats */ nullptr,
- null_dex_cache_,
+ handles->NewHandle<mirror::DexCache>(nullptr),
handles) {}
GraphAnalysisResult BuildGraph();
@@ -117,8 +122,6 @@
CompilerDriver* const compiler_driver_;
- ScopedNullHandle<mirror::DexCache> null_dex_cache_;
-
OptimizingCompilerStats* compilation_stats_;
HBasicBlockBuilder block_builder_;
diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc
index 759a951..7b84ef8 100644
--- a/compiler/optimizing/code_generator_arm.cc
+++ b/compiler/optimizing/code_generator_arm.cc
@@ -19,6 +19,7 @@
#include "arch/arm/instruction_set_features_arm.h"
#include "art_method.h"
#include "code_generator_utils.h"
+#include "common_arm.h"
#include "compiled_method.h"
#include "entrypoints/quick/quick_entrypoints.h"
#include "gc/accounting/card_table.h"
@@ -1132,10 +1133,6 @@
DISALLOW_COPY_AND_ASSIGN(ReadBarrierForRootSlowPathARM);
};
-#undef __
-// NOLINT on __ macro to suppress wrong warning/fix (misc-macro-parentheses) from clang-tidy.
-#define __ down_cast<ArmAssembler*>(GetAssembler())-> // NOLINT
-
inline Condition ARMCondition(IfCondition cond) {
switch (cond) {
case kCondEQ: return EQ;
@@ -1191,6 +1188,197 @@
}
}
+inline Shift ShiftFromOpKind(HDataProcWithShifterOp::OpKind op_kind) {
+ switch (op_kind) {
+ case HDataProcWithShifterOp::kASR: return ASR;
+ case HDataProcWithShifterOp::kLSL: return LSL;
+ case HDataProcWithShifterOp::kLSR: return LSR;
+ default:
+ LOG(FATAL) << "Unexpected op kind " << op_kind;
+ UNREACHABLE();
+ }
+}
+
+static void GenerateDataProcInstruction(HInstruction::InstructionKind kind,
+ Register out,
+ Register first,
+ const ShifterOperand& second,
+ CodeGeneratorARM* codegen) {
+ if (second.IsImmediate() && second.GetImmediate() == 0) {
+ const ShifterOperand in = kind == HInstruction::kAnd
+ ? ShifterOperand(0)
+ : ShifterOperand(first);
+
+ __ mov(out, in);
+ } else {
+ switch (kind) {
+ case HInstruction::kAdd:
+ __ add(out, first, second);
+ break;
+ case HInstruction::kAnd:
+ __ and_(out, first, second);
+ break;
+ case HInstruction::kOr:
+ __ orr(out, first, second);
+ break;
+ case HInstruction::kSub:
+ __ sub(out, first, second);
+ break;
+ case HInstruction::kXor:
+ __ eor(out, first, second);
+ break;
+ default:
+ LOG(FATAL) << "Unexpected instruction kind: " << kind;
+ UNREACHABLE();
+ }
+ }
+}
+
+static void GenerateDataProc(HInstruction::InstructionKind kind,
+ const Location& out,
+ const Location& first,
+ const ShifterOperand& second_lo,
+ const ShifterOperand& second_hi,
+ CodeGeneratorARM* codegen) {
+ const Register first_hi = first.AsRegisterPairHigh<Register>();
+ const Register first_lo = first.AsRegisterPairLow<Register>();
+ const Register out_hi = out.AsRegisterPairHigh<Register>();
+ const Register out_lo = out.AsRegisterPairLow<Register>();
+
+ if (kind == HInstruction::kAdd) {
+ __ adds(out_lo, first_lo, second_lo);
+ __ adc(out_hi, first_hi, second_hi);
+ } else if (kind == HInstruction::kSub) {
+ __ subs(out_lo, first_lo, second_lo);
+ __ sbc(out_hi, first_hi, second_hi);
+ } else {
+ GenerateDataProcInstruction(kind, out_lo, first_lo, second_lo, codegen);
+ GenerateDataProcInstruction(kind, out_hi, first_hi, second_hi, codegen);
+ }
+}
+
+static ShifterOperand GetShifterOperand(Register rm, Shift shift, uint32_t shift_imm) {
+ return shift_imm == 0 ? ShifterOperand(rm) : ShifterOperand(rm, shift, shift_imm);
+}
+
+static void GenerateLongDataProc(HDataProcWithShifterOp* instruction, CodeGeneratorARM* codegen) {
+ DCHECK_EQ(instruction->GetType(), Primitive::kPrimLong);
+ DCHECK(HDataProcWithShifterOp::IsShiftOp(instruction->GetOpKind()));
+
+ const LocationSummary* const locations = instruction->GetLocations();
+ const uint32_t shift_value = instruction->GetShiftAmount();
+ const HInstruction::InstructionKind kind = instruction->GetInstrKind();
+ const Location first = locations->InAt(0);
+ const Location second = locations->InAt(1);
+ const Location out = locations->Out();
+ const Register first_hi = first.AsRegisterPairHigh<Register>();
+ const Register first_lo = first.AsRegisterPairLow<Register>();
+ const Register out_hi = out.AsRegisterPairHigh<Register>();
+ const Register out_lo = out.AsRegisterPairLow<Register>();
+ const Register second_hi = second.AsRegisterPairHigh<Register>();
+ const Register second_lo = second.AsRegisterPairLow<Register>();
+ const Shift shift = ShiftFromOpKind(instruction->GetOpKind());
+
+ if (shift_value >= 32) {
+ if (shift == LSL) {
+ GenerateDataProcInstruction(kind,
+ out_hi,
+ first_hi,
+ ShifterOperand(second_lo, LSL, shift_value - 32),
+ codegen);
+ GenerateDataProcInstruction(kind,
+ out_lo,
+ first_lo,
+ ShifterOperand(0),
+ codegen);
+ } else if (shift == ASR) {
+ GenerateDataProc(kind,
+ out,
+ first,
+ GetShifterOperand(second_hi, ASR, shift_value - 32),
+ ShifterOperand(second_hi, ASR, 31),
+ codegen);
+ } else {
+ DCHECK_EQ(shift, LSR);
+ GenerateDataProc(kind,
+ out,
+ first,
+ GetShifterOperand(second_hi, LSR, shift_value - 32),
+ ShifterOperand(0),
+ codegen);
+ }
+ } else {
+ DCHECK_GT(shift_value, 1U);
+ DCHECK_LT(shift_value, 32U);
+
+ if (shift == LSL) {
+ // We are not doing this for HInstruction::kAdd because the output will require
+ // Location::kOutputOverlap; not applicable to other cases.
+ if (kind == HInstruction::kOr || kind == HInstruction::kXor) {
+ GenerateDataProcInstruction(kind,
+ out_hi,
+ first_hi,
+ ShifterOperand(second_hi, LSL, shift_value),
+ codegen);
+ GenerateDataProcInstruction(kind,
+ out_hi,
+ out_hi,
+ ShifterOperand(second_lo, LSR, 32 - shift_value),
+ codegen);
+ GenerateDataProcInstruction(kind,
+ out_lo,
+ first_lo,
+ ShifterOperand(second_lo, LSL, shift_value),
+ codegen);
+ } else {
+ __ Lsl(IP, second_hi, shift_value);
+ __ orr(IP, IP, ShifterOperand(second_lo, LSR, 32 - shift_value));
+ GenerateDataProc(kind,
+ out,
+ first,
+ ShifterOperand(second_lo, LSL, shift_value),
+ ShifterOperand(IP),
+ codegen);
+ }
+ } else {
+ DCHECK(shift == ASR || shift == LSR);
+
+ // We are not doing this for HInstruction::kAdd because the output will require
+ // Location::kOutputOverlap; not applicable to other cases.
+ if (kind == HInstruction::kOr || kind == HInstruction::kXor) {
+ GenerateDataProcInstruction(kind,
+ out_lo,
+ first_lo,
+ ShifterOperand(second_lo, LSR, shift_value),
+ codegen);
+ GenerateDataProcInstruction(kind,
+ out_lo,
+ out_lo,
+ ShifterOperand(second_hi, LSL, 32 - shift_value),
+ codegen);
+ GenerateDataProcInstruction(kind,
+ out_hi,
+ first_hi,
+ ShifterOperand(second_hi, shift, shift_value),
+ codegen);
+ } else {
+ __ Lsr(IP, second_lo, shift_value);
+ __ orr(IP, IP, ShifterOperand(second_hi, LSL, 32 - shift_value));
+ GenerateDataProc(kind,
+ out,
+ first,
+ ShifterOperand(IP),
+ ShifterOperand(second_hi, shift, shift_value),
+ codegen);
+ }
+ }
+ }
+}
+
+#undef __
+// NOLINT on __ macro to suppress wrong warning/fix (misc-macro-parentheses) from clang-tidy.
+#define __ down_cast<ArmAssembler*>(GetAssembler())-> // NOLINT
+
void CodeGeneratorARM::DumpCoreRegister(std::ostream& stream, int reg) const {
stream << Register(reg);
}
@@ -6709,6 +6897,63 @@
}
}
+void LocationsBuilderARM::VisitDataProcWithShifterOp(
+ HDataProcWithShifterOp* instruction) {
+ DCHECK(instruction->GetType() == Primitive::kPrimInt ||
+ instruction->GetType() == Primitive::kPrimLong);
+ LocationSummary* locations =
+ new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kNoCall);
+ const bool overlap = instruction->GetType() == Primitive::kPrimLong &&
+ HDataProcWithShifterOp::IsExtensionOp(instruction->GetOpKind());
+
+ locations->SetInAt(0, Location::RequiresRegister());
+ locations->SetInAt(1, Location::RequiresRegister());
+ locations->SetOut(Location::RequiresRegister(),
+ overlap ? Location::kOutputOverlap : Location::kNoOutputOverlap);
+}
+
+void InstructionCodeGeneratorARM::VisitDataProcWithShifterOp(
+ HDataProcWithShifterOp* instruction) {
+ const LocationSummary* const locations = instruction->GetLocations();
+ const HInstruction::InstructionKind kind = instruction->GetInstrKind();
+ const HDataProcWithShifterOp::OpKind op_kind = instruction->GetOpKind();
+ const Location left = locations->InAt(0);
+ const Location right = locations->InAt(1);
+ const Location out = locations->Out();
+
+ if (instruction->GetType() == Primitive::kPrimInt) {
+ DCHECK(!HDataProcWithShifterOp::IsExtensionOp(op_kind));
+
+ const Register second = instruction->InputAt(1)->GetType() == Primitive::kPrimLong
+ ? right.AsRegisterPairLow<Register>()
+ : right.AsRegister<Register>();
+
+ GenerateDataProcInstruction(kind,
+ out.AsRegister<Register>(),
+ left.AsRegister<Register>(),
+ ShifterOperand(second,
+ ShiftFromOpKind(op_kind),
+ instruction->GetShiftAmount()),
+ codegen_);
+ } else {
+ DCHECK_EQ(instruction->GetType(), Primitive::kPrimLong);
+
+ if (HDataProcWithShifterOp::IsExtensionOp(op_kind)) {
+ const Register second = right.AsRegister<Register>();
+
+ DCHECK_NE(out.AsRegisterPairLow<Register>(), second);
+ GenerateDataProc(kind,
+ out,
+ left,
+ ShifterOperand(second),
+ ShifterOperand(second, ASR, 31),
+ codegen_);
+ } else {
+ GenerateLongDataProc(instruction, codegen_);
+ }
+ }
+}
+
void InstructionCodeGeneratorARM::GenerateAndConst(Register out, Register first, uint32_t value) {
// Optimize special cases for individual halfs of `and-long` (`and` is simplified earlier).
if (value == 0xffffffffu) {
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index e6032d2..edccbd4 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -2277,8 +2277,8 @@
}
}
-void LocationsBuilderARM64::VisitArm64DataProcWithShifterOp(
- HArm64DataProcWithShifterOp* instruction) {
+void LocationsBuilderARM64::VisitDataProcWithShifterOp(
+ HDataProcWithShifterOp* instruction) {
DCHECK(instruction->GetType() == Primitive::kPrimInt ||
instruction->GetType() == Primitive::kPrimLong);
LocationSummary* locations =
@@ -2292,8 +2292,8 @@
locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap);
}
-void InstructionCodeGeneratorARM64::VisitArm64DataProcWithShifterOp(
- HArm64DataProcWithShifterOp* instruction) {
+void InstructionCodeGeneratorARM64::VisitDataProcWithShifterOp(
+ HDataProcWithShifterOp* instruction) {
Primitive::Type type = instruction->GetType();
HInstruction::InstructionKind kind = instruction->GetInstrKind();
DCHECK(type == Primitive::kPrimInt || type == Primitive::kPrimLong);
@@ -2302,21 +2302,20 @@
if (kind != HInstruction::kNeg) {
left = InputRegisterAt(instruction, 0);
}
- // If this `HArm64DataProcWithShifterOp` was created by merging a type conversion as the
+ // If this `HDataProcWithShifterOp` was created by merging a type conversion as the
// shifter operand operation, the IR generating `right_reg` (input to the type
// conversion) can have a different type from the current instruction's type,
// so we manually indicate the type.
Register right_reg = RegisterFrom(instruction->GetLocations()->InAt(1), type);
- int64_t shift_amount = instruction->GetShiftAmount() &
- (type == Primitive::kPrimInt ? kMaxIntShiftDistance : kMaxLongShiftDistance);
-
Operand right_operand(0);
- HArm64DataProcWithShifterOp::OpKind op_kind = instruction->GetOpKind();
- if (HArm64DataProcWithShifterOp::IsExtensionOp(op_kind)) {
+ HDataProcWithShifterOp::OpKind op_kind = instruction->GetOpKind();
+ if (HDataProcWithShifterOp::IsExtensionOp(op_kind)) {
right_operand = Operand(right_reg, helpers::ExtendFromOpKind(op_kind));
} else {
- right_operand = Operand(right_reg, helpers::ShiftFromOpKind(op_kind), shift_amount);
+ right_operand = Operand(right_reg,
+ helpers::ShiftFromOpKind(op_kind),
+ instruction->GetShiftAmount());
}
// Logical binary operations do not support extension operations in the
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index 5c4ca5b..6bfbe4a 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -1216,6 +1216,17 @@
}
}
+inline ShiftType ShiftFromOpKind(HDataProcWithShifterOp::OpKind op_kind) {
+ switch (op_kind) {
+ case HDataProcWithShifterOp::kASR: return ShiftType::ASR;
+ case HDataProcWithShifterOp::kLSL: return ShiftType::LSL;
+ case HDataProcWithShifterOp::kLSR: return ShiftType::LSR;
+ default:
+ LOG(FATAL) << "Unexpected op kind " << op_kind;
+ UNREACHABLE();
+ }
+}
+
void CodeGeneratorARMVIXL::DumpCoreRegister(std::ostream& stream, int reg) const {
stream << vixl32::Register(reg);
}
@@ -1260,6 +1271,185 @@
return 0;
}
+static void GenerateDataProcInstruction(HInstruction::InstructionKind kind,
+ vixl32::Register out,
+ vixl32::Register first,
+ const Operand& second,
+ CodeGeneratorARMVIXL* codegen) {
+ if (second.IsImmediate() && second.GetImmediate() == 0) {
+ const Operand in = kind == HInstruction::kAnd
+ ? Operand(0)
+ : Operand(first);
+
+ __ Mov(out, in);
+ } else {
+ switch (kind) {
+ case HInstruction::kAdd:
+ __ Add(out, first, second);
+ break;
+ case HInstruction::kAnd:
+ __ And(out, first, second);
+ break;
+ case HInstruction::kOr:
+ __ Orr(out, first, second);
+ break;
+ case HInstruction::kSub:
+ __ Sub(out, first, second);
+ break;
+ case HInstruction::kXor:
+ __ Eor(out, first, second);
+ break;
+ default:
+ LOG(FATAL) << "Unexpected instruction kind: " << kind;
+ UNREACHABLE();
+ }
+ }
+}
+
+static void GenerateDataProc(HInstruction::InstructionKind kind,
+ const Location& out,
+ const Location& first,
+ const Operand& second_lo,
+ const Operand& second_hi,
+ CodeGeneratorARMVIXL* codegen) {
+ const vixl32::Register first_hi = HighRegisterFrom(first);
+ const vixl32::Register first_lo = LowRegisterFrom(first);
+ const vixl32::Register out_hi = HighRegisterFrom(out);
+ const vixl32::Register out_lo = LowRegisterFrom(out);
+
+ if (kind == HInstruction::kAdd) {
+ __ Adds(out_lo, first_lo, second_lo);
+ __ Adc(out_hi, first_hi, second_hi);
+ } else if (kind == HInstruction::kSub) {
+ __ Subs(out_lo, first_lo, second_lo);
+ __ Sbc(out_hi, first_hi, second_hi);
+ } else {
+ GenerateDataProcInstruction(kind, out_lo, first_lo, second_lo, codegen);
+ GenerateDataProcInstruction(kind, out_hi, first_hi, second_hi, codegen);
+ }
+}
+
+static Operand GetShifterOperand(vixl32::Register rm, ShiftType shift, uint32_t shift_imm) {
+ return shift_imm == 0 ? Operand(rm) : Operand(rm, shift, shift_imm);
+}
+
+static void GenerateLongDataProc(HDataProcWithShifterOp* instruction,
+ CodeGeneratorARMVIXL* codegen) {
+ DCHECK_EQ(instruction->GetType(), Primitive::kPrimLong);
+ DCHECK(HDataProcWithShifterOp::IsShiftOp(instruction->GetOpKind()));
+
+ const LocationSummary* const locations = instruction->GetLocations();
+ const uint32_t shift_value = instruction->GetShiftAmount();
+ const HInstruction::InstructionKind kind = instruction->GetInstrKind();
+ const Location first = locations->InAt(0);
+ const Location second = locations->InAt(1);
+ const Location out = locations->Out();
+ const vixl32::Register first_hi = HighRegisterFrom(first);
+ const vixl32::Register first_lo = LowRegisterFrom(first);
+ const vixl32::Register out_hi = HighRegisterFrom(out);
+ const vixl32::Register out_lo = LowRegisterFrom(out);
+ const vixl32::Register second_hi = HighRegisterFrom(second);
+ const vixl32::Register second_lo = LowRegisterFrom(second);
+ const ShiftType shift = ShiftFromOpKind(instruction->GetOpKind());
+
+ if (shift_value >= 32) {
+ if (shift == ShiftType::LSL) {
+ GenerateDataProcInstruction(kind,
+ out_hi,
+ first_hi,
+ Operand(second_lo, ShiftType::LSL, shift_value - 32),
+ codegen);
+ GenerateDataProcInstruction(kind, out_lo, first_lo, 0, codegen);
+ } else if (shift == ShiftType::ASR) {
+ GenerateDataProc(kind,
+ out,
+ first,
+ GetShifterOperand(second_hi, ShiftType::ASR, shift_value - 32),
+ Operand(second_hi, ShiftType::ASR, 31),
+ codegen);
+ } else {
+ DCHECK_EQ(shift, ShiftType::LSR);
+ GenerateDataProc(kind,
+ out,
+ first,
+ GetShifterOperand(second_hi, ShiftType::LSR, shift_value - 32),
+ 0,
+ codegen);
+ }
+ } else {
+ DCHECK_GT(shift_value, 1U);
+ DCHECK_LT(shift_value, 32U);
+
+ UseScratchRegisterScope temps(codegen->GetVIXLAssembler());
+
+ if (shift == ShiftType::LSL) {
+ // We are not doing this for HInstruction::kAdd because the output will require
+ // Location::kOutputOverlap; not applicable to other cases.
+ if (kind == HInstruction::kOr || kind == HInstruction::kXor) {
+ GenerateDataProcInstruction(kind,
+ out_hi,
+ first_hi,
+ Operand(second_hi, ShiftType::LSL, shift_value),
+ codegen);
+ GenerateDataProcInstruction(kind,
+ out_hi,
+ out_hi,
+ Operand(second_lo, ShiftType::LSR, 32 - shift_value),
+ codegen);
+ GenerateDataProcInstruction(kind,
+ out_lo,
+ first_lo,
+ Operand(second_lo, ShiftType::LSL, shift_value),
+ codegen);
+ } else {
+ const vixl32::Register temp = temps.Acquire();
+
+ __ Lsl(temp, second_hi, shift_value);
+ __ Orr(temp, temp, Operand(second_lo, ShiftType::LSR, 32 - shift_value));
+ GenerateDataProc(kind,
+ out,
+ first,
+ Operand(second_lo, ShiftType::LSL, shift_value),
+ temp,
+ codegen);
+ }
+ } else {
+ DCHECK(shift == ShiftType::ASR || shift == ShiftType::LSR);
+
+ // We are not doing this for HInstruction::kAdd because the output will require
+ // Location::kOutputOverlap; not applicable to other cases.
+ if (kind == HInstruction::kOr || kind == HInstruction::kXor) {
+ GenerateDataProcInstruction(kind,
+ out_lo,
+ first_lo,
+ Operand(second_lo, ShiftType::LSR, shift_value),
+ codegen);
+ GenerateDataProcInstruction(kind,
+ out_lo,
+ out_lo,
+ Operand(second_hi, ShiftType::LSL, 32 - shift_value),
+ codegen);
+ GenerateDataProcInstruction(kind,
+ out_hi,
+ first_hi,
+ Operand(second_hi, shift, shift_value),
+ codegen);
+ } else {
+ const vixl32::Register temp = temps.Acquire();
+
+ __ Lsr(temp, second_lo, shift_value);
+ __ Orr(temp, temp, Operand(second_hi, ShiftType::LSL, 32 - shift_value));
+ GenerateDataProc(kind,
+ out,
+ first,
+ temp,
+ Operand(second_hi, shift, shift_value),
+ codegen);
+ }
+ }
+ }
+}
+
#undef __
CodeGeneratorARMVIXL::CodeGeneratorARMVIXL(HGraph* graph,
@@ -6781,6 +6971,60 @@
}
}
+void LocationsBuilderARMVIXL::VisitDataProcWithShifterOp(
+ HDataProcWithShifterOp* instruction) {
+ DCHECK(instruction->GetType() == Primitive::kPrimInt ||
+ instruction->GetType() == Primitive::kPrimLong);
+ LocationSummary* locations =
+ new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kNoCall);
+ const bool overlap = instruction->GetType() == Primitive::kPrimLong &&
+ HDataProcWithShifterOp::IsExtensionOp(instruction->GetOpKind());
+
+ locations->SetInAt(0, Location::RequiresRegister());
+ locations->SetInAt(1, Location::RequiresRegister());
+ locations->SetOut(Location::RequiresRegister(),
+ overlap ? Location::kOutputOverlap : Location::kNoOutputOverlap);
+}
+
+void InstructionCodeGeneratorARMVIXL::VisitDataProcWithShifterOp(
+ HDataProcWithShifterOp* instruction) {
+ const LocationSummary* const locations = instruction->GetLocations();
+ const HInstruction::InstructionKind kind = instruction->GetInstrKind();
+ const HDataProcWithShifterOp::OpKind op_kind = instruction->GetOpKind();
+
+ if (instruction->GetType() == Primitive::kPrimInt) {
+ DCHECK(!HDataProcWithShifterOp::IsExtensionOp(op_kind));
+
+ const vixl32::Register second = instruction->InputAt(1)->GetType() == Primitive::kPrimLong
+ ? LowRegisterFrom(locations->InAt(1))
+ : InputRegisterAt(instruction, 1);
+
+ GenerateDataProcInstruction(kind,
+ OutputRegister(instruction),
+ InputRegisterAt(instruction, 0),
+ Operand(second,
+ ShiftFromOpKind(op_kind),
+ instruction->GetShiftAmount()),
+ codegen_);
+ } else {
+ DCHECK_EQ(instruction->GetType(), Primitive::kPrimLong);
+
+ if (HDataProcWithShifterOp::IsExtensionOp(op_kind)) {
+ const vixl32::Register second = InputRegisterAt(instruction, 1);
+
+ DCHECK(!LowRegisterFrom(locations->Out()).Is(second));
+ GenerateDataProc(kind,
+ locations->Out(),
+ locations->InAt(0),
+ second,
+ Operand(second, ShiftType::ASR, 31),
+ codegen_);
+ } else {
+ GenerateLongDataProc(instruction, codegen_);
+ }
+ }
+}
+
// TODO(VIXL): Remove optimizations in the helper when they are implemented in vixl.
void InstructionCodeGeneratorARMVIXL::GenerateAndConst(vixl32::Register out,
vixl32::Register first,
diff --git a/compiler/optimizing/code_generator_arm_vixl.h b/compiler/optimizing/code_generator_arm_vixl.h
index 8ae3b7d..3f52c72 100644
--- a/compiler/optimizing/code_generator_arm_vixl.h
+++ b/compiler/optimizing/code_generator_arm_vixl.h
@@ -35,11 +35,11 @@
#include "aarch32/macro-assembler-aarch32.h"
#pragma GCC diagnostic pop
-// True if VIXL32 should be used for codegen on ARM.
-#ifdef ART_USE_VIXL_ARM_BACKEND
-static constexpr bool kArmUseVIXL32 = true;
-#else
+// Default to use the VIXL-based backend on ARM.
+#ifdef ART_USE_OLD_ARM_BACKEND
static constexpr bool kArmUseVIXL32 = false;
+#else
+static constexpr bool kArmUseVIXL32 = true;
#endif
namespace art {
diff --git a/compiler/optimizing/common_arm.h b/compiler/optimizing/common_arm.h
index ecb8687..e184745 100644
--- a/compiler/optimizing/common_arm.h
+++ b/compiler/optimizing/common_arm.h
@@ -17,6 +17,7 @@
#ifndef ART_COMPILER_OPTIMIZING_COMMON_ARM_H_
#define ART_COMPILER_OPTIMIZING_COMMON_ARM_H_
+#include "instruction_simplifier_shared.h"
#include "debug/dwarf/register.h"
#include "locations.h"
#include "nodes.h"
@@ -29,6 +30,9 @@
#pragma GCC diagnostic pop
namespace art {
+
+using helpers::HasShifterOperand;
+
namespace arm {
namespace helpers {
@@ -218,6 +222,14 @@
return Location::FpuRegisterPairLocation(low.GetCode(), high.GetCode());
}
+inline bool ShifterOperandSupportsExtension(HInstruction* instruction) {
+ DCHECK(HasShifterOperand(instruction, kArm));
+ // TODO: HAdd applied to the other integral types could make use of
+ // the SXTAB, SXTAH, UXTAB and UXTAH instructions.
+ return instruction->GetType() == Primitive::kPrimLong &&
+ (instruction->IsAdd() || instruction->IsSub());
+}
+
} // namespace helpers
} // namespace arm
} // namespace art
diff --git a/compiler/optimizing/common_arm64.h b/compiler/optimizing/common_arm64.h
index 93ea090..d3f431e 100644
--- a/compiler/optimizing/common_arm64.h
+++ b/compiler/optimizing/common_arm64.h
@@ -18,6 +18,7 @@
#define ART_COMPILER_OPTIMIZING_COMMON_ARM64_H_
#include "code_generator.h"
+#include "instruction_simplifier_shared.h"
#include "locations.h"
#include "nodes.h"
#include "utils/arm64/assembler_arm64.h"
@@ -31,6 +32,10 @@
#pragma GCC diagnostic pop
namespace art {
+
+using helpers::CanFitInShifterOperand;
+using helpers::HasShifterOperand;
+
namespace arm64 {
namespace helpers {
@@ -290,11 +295,11 @@
return true;
}
-inline vixl::aarch64::Shift ShiftFromOpKind(HArm64DataProcWithShifterOp::OpKind op_kind) {
+inline vixl::aarch64::Shift ShiftFromOpKind(HDataProcWithShifterOp::OpKind op_kind) {
switch (op_kind) {
- case HArm64DataProcWithShifterOp::kASR: return vixl::aarch64::ASR;
- case HArm64DataProcWithShifterOp::kLSL: return vixl::aarch64::LSL;
- case HArm64DataProcWithShifterOp::kLSR: return vixl::aarch64::LSR;
+ case HDataProcWithShifterOp::kASR: return vixl::aarch64::ASR;
+ case HDataProcWithShifterOp::kLSL: return vixl::aarch64::LSL;
+ case HDataProcWithShifterOp::kLSR: return vixl::aarch64::LSR;
default:
LOG(FATAL) << "Unexpected op kind " << op_kind;
UNREACHABLE();
@@ -302,14 +307,14 @@
}
}
-inline vixl::aarch64::Extend ExtendFromOpKind(HArm64DataProcWithShifterOp::OpKind op_kind) {
+inline vixl::aarch64::Extend ExtendFromOpKind(HDataProcWithShifterOp::OpKind op_kind) {
switch (op_kind) {
- case HArm64DataProcWithShifterOp::kUXTB: return vixl::aarch64::UXTB;
- case HArm64DataProcWithShifterOp::kUXTH: return vixl::aarch64::UXTH;
- case HArm64DataProcWithShifterOp::kUXTW: return vixl::aarch64::UXTW;
- case HArm64DataProcWithShifterOp::kSXTB: return vixl::aarch64::SXTB;
- case HArm64DataProcWithShifterOp::kSXTH: return vixl::aarch64::SXTH;
- case HArm64DataProcWithShifterOp::kSXTW: return vixl::aarch64::SXTW;
+ case HDataProcWithShifterOp::kUXTB: return vixl::aarch64::UXTB;
+ case HDataProcWithShifterOp::kUXTH: return vixl::aarch64::UXTH;
+ case HDataProcWithShifterOp::kUXTW: return vixl::aarch64::UXTW;
+ case HDataProcWithShifterOp::kSXTB: return vixl::aarch64::SXTB;
+ case HDataProcWithShifterOp::kSXTH: return vixl::aarch64::SXTH;
+ case HDataProcWithShifterOp::kSXTW: return vixl::aarch64::SXTW;
default:
LOG(FATAL) << "Unexpected op kind " << op_kind;
UNREACHABLE();
@@ -317,31 +322,8 @@
}
}
-inline bool CanFitInShifterOperand(HInstruction* instruction) {
- if (instruction->IsTypeConversion()) {
- HTypeConversion* conversion = instruction->AsTypeConversion();
- Primitive::Type result_type = conversion->GetResultType();
- Primitive::Type input_type = conversion->GetInputType();
- // We don't expect to see the same type as input and result.
- return Primitive::IsIntegralType(result_type) && Primitive::IsIntegralType(input_type) &&
- (result_type != input_type);
- } else {
- return (instruction->IsShl() && instruction->AsShl()->InputAt(1)->IsIntConstant()) ||
- (instruction->IsShr() && instruction->AsShr()->InputAt(1)->IsIntConstant()) ||
- (instruction->IsUShr() && instruction->AsUShr()->InputAt(1)->IsIntConstant());
- }
-}
-
-inline bool HasShifterOperand(HInstruction* instr) {
- // `neg` instructions are an alias of `sub` using the zero register as the
- // first register input.
- bool res = instr->IsAdd() || instr->IsAnd() || instr->IsNeg() ||
- instr->IsOr() || instr->IsSub() || instr->IsXor();
- return res;
-}
-
inline bool ShifterOperandSupportsExtension(HInstruction* instruction) {
- DCHECK(HasShifterOperand(instruction));
+ DCHECK(HasShifterOperand(instruction, kArm64));
// Although the `neg` instruction is an alias of the `sub` instruction, `HNeg`
// does *not* support extension. This is because the `extended register` form
// of the `sub` instruction interprets the left register with code 31 as the
diff --git a/compiler/optimizing/dex_cache_array_fixups_arm.cc b/compiler/optimizing/dex_cache_array_fixups_arm.cc
index cfcb276..0c832a5 100644
--- a/compiler/optimizing/dex_cache_array_fixups_arm.cc
+++ b/compiler/optimizing/dex_cache_array_fixups_arm.cc
@@ -17,23 +17,23 @@
#include "dex_cache_array_fixups_arm.h"
#include "base/arena_containers.h"
-#ifdef ART_USE_VIXL_ARM_BACKEND
-#include "code_generator_arm_vixl.h"
-#include "intrinsics_arm_vixl.h"
-#else
+#ifdef ART_USE_OLD_ARM_BACKEND
#include "code_generator_arm.h"
#include "intrinsics_arm.h"
+#else
+#include "code_generator_arm_vixl.h"
+#include "intrinsics_arm_vixl.h"
#endif
#include "utils/dex_cache_arrays_layout-inl.h"
namespace art {
namespace arm {
-#ifdef ART_USE_VIXL_ARM_BACKEND
-typedef CodeGeneratorARMVIXL CodeGeneratorARMType;
-typedef IntrinsicLocationsBuilderARMVIXL IntrinsicLocationsBuilderARMType;
-#else
+#ifdef ART_USE_OLD_ARM_BACKEND
typedef CodeGeneratorARM CodeGeneratorARMType;
typedef IntrinsicLocationsBuilderARM IntrinsicLocationsBuilderARMType;
+#else
+typedef CodeGeneratorARMVIXL CodeGeneratorARMType;
+typedef IntrinsicLocationsBuilderARMVIXL IntrinsicLocationsBuilderARMType;
#endif
/**
diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc
index f6fba88..2bf5c53 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -511,12 +511,10 @@
void VisitBitwiseNegatedRight(HBitwiseNegatedRight* instruction) OVERRIDE {
StartAttributeStream("kind") << instruction->GetOpKind();
}
-#endif
-#ifdef ART_ENABLE_CODEGEN_arm64
- void VisitArm64DataProcWithShifterOp(HArm64DataProcWithShifterOp* instruction) OVERRIDE {
+ void VisitDataProcWithShifterOp(HDataProcWithShifterOp* instruction) OVERRIDE {
StartAttributeStream("kind") << instruction->GetInstrKind() << "+" << instruction->GetOpKind();
- if (HArm64DataProcWithShifterOp::IsShiftOp(instruction->GetOpKind())) {
+ if (HDataProcWithShifterOp::IsShiftOp(instruction->GetOpKind())) {
StartAttributeStream("shift") << instruction->GetShiftAmount();
}
}
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index e012a42..8c73f1d 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -192,9 +192,9 @@
}
static dex::TypeIndex FindClassIndexIn(mirror::Class* cls,
- const DexFile& dex_file,
- Handle<mirror::DexCache> dex_cache)
+ const DexCompilationUnit& compilation_unit)
REQUIRES_SHARED(Locks::mutator_lock_) {
+ const DexFile& dex_file = *compilation_unit.GetDexFile();
dex::TypeIndex index;
if (cls->GetDexCache() == nullptr) {
DCHECK(cls->IsArrayClass()) << cls->PrettyClass();
@@ -203,22 +203,19 @@
DCHECK(cls->IsProxyClass()) << cls->PrettyClass();
// TODO: deal with proxy classes.
} else if (IsSameDexFile(cls->GetDexFile(), dex_file)) {
- DCHECK_EQ(cls->GetDexCache(), dex_cache.Get());
+ DCHECK_EQ(cls->GetDexCache(), compilation_unit.GetDexCache().Get());
index = cls->GetDexTypeIndex();
- // Update the dex cache to ensure the class is in. The generated code will
- // consider it is. We make it safe by updating the dex cache, as other
- // dex files might also load the class, and there is no guarantee the dex
- // cache of the dex file of the class will be updated.
- if (dex_cache->GetResolvedType(index) == nullptr) {
- dex_cache->SetResolvedType(index, cls);
- }
} else {
index = cls->FindTypeIndexInOtherDexFile(dex_file);
- // We cannot guarantee the entry in the dex cache will resolve to the same class,
+ // We cannot guarantee the entry will resolve to the same class,
// as there may be different class loaders. So only return the index if it's
- // the right class in the dex cache already.
- if (index.IsValid() && dex_cache->GetResolvedType(index) != cls) {
- index = dex::TypeIndex::Invalid();
+ // the right class already resolved with the class loader.
+ if (index.IsValid()) {
+ ObjPtr<mirror::Class> resolved = ClassLinker::LookupResolvedType(
+ index, compilation_unit.GetDexCache().Get(), compilation_unit.GetClassLoader().Get());
+ if (resolved != cls) {
+ index = dex::TypeIndex::Invalid();
+ }
}
}
@@ -445,9 +442,8 @@
DCHECK(invoke_instruction->IsInvokeVirtual() || invoke_instruction->IsInvokeInterface())
<< invoke_instruction->DebugName();
- const DexFile& caller_dex_file = *caller_compilation_unit_.GetDexFile();
dex::TypeIndex class_index = FindClassIndexIn(
- GetMonomorphicType(classes), caller_dex_file, caller_compilation_unit_.GetDexCache());
+ GetMonomorphicType(classes), caller_compilation_unit_);
if (!class_index.IsValid()) {
VLOG(compiler) << "Call to " << ArtMethod::PrettyMethod(resolved_method)
<< " from inline cache is not inlined because its class is not"
@@ -490,6 +486,7 @@
// 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(),
handles_,
/* is_first_run */ false);
@@ -583,7 +580,6 @@
ClassLinker* class_linker = caller_compilation_unit_.GetClassLinker();
PointerSize pointer_size = class_linker->GetImagePointerSize();
- const DexFile& caller_dex_file = *caller_compilation_unit_.GetDexFile();
bool all_targets_inlined = true;
bool one_target_inlined = false;
@@ -605,8 +601,7 @@
HInstruction* cursor = invoke_instruction->GetPrevious();
HBasicBlock* bb_cursor = invoke_instruction->GetBlock();
- dex::TypeIndex class_index = FindClassIndexIn(
- handle.Get(), caller_dex_file, caller_compilation_unit_.GetDexCache());
+ dex::TypeIndex class_index = FindClassIndexIn(handle.Get(), caller_compilation_unit_);
HInstruction* return_replacement = nullptr;
if (!class_index.IsValid() ||
!TryBuildAndInline(invoke_instruction,
@@ -662,6 +657,7 @@
// Run type propagation to get the guards typed.
ReferenceTypePropagation rtp_fixup(graph_,
+ outer_compilation_unit_.GetClassLoader(),
outer_compilation_unit_.GetDexCache(),
handles_,
/* is_first_run */ false);
@@ -855,6 +851,7 @@
// Run type propagation to get the guard typed.
ReferenceTypePropagation rtp_fixup(graph_,
+ outer_compilation_unit_.GetClassLoader(),
outer_compilation_unit_.GetDexCache(),
handles_,
/* is_first_run */ false);
@@ -923,6 +920,7 @@
// 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(),
handles_,
/* is_first_run */ false).Run();
@@ -1175,7 +1173,11 @@
/* dex_pc */ 0);
if (iget->GetType() == Primitive::kPrimNot) {
// Use the same dex_cache that we used for field lookup as the hint_dex_cache.
- ReferenceTypePropagation rtp(graph_, dex_cache, handles_, /* is_first_run */ false);
+ ReferenceTypePropagation rtp(graph_,
+ outer_compilation_unit_.GetClassLoader(),
+ dex_cache,
+ handles_,
+ /* is_first_run */ false);
rtp.Visit(iget);
}
return iget;
@@ -1221,7 +1223,7 @@
resolved_method->GetDeclaringClass()->GetClassLoader()));
DexCompilationUnit dex_compilation_unit(
- class_loader.ToJObject(),
+ class_loader,
class_linker,
callee_dex_file,
code_item,
@@ -1338,6 +1340,7 @@
// 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(),
handles_,
/* is_first_run */ false).Run();
diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc
index 3374e42..c60f6e5 100644
--- a/compiler/optimizing/instruction_builder.cc
+++ b/compiler/optimizing/instruction_builder.cc
@@ -669,11 +669,10 @@
ArtMethod* HInstructionBuilder::ResolveMethod(uint16_t method_idx, InvokeType invoke_type) {
ScopedObjectAccess soa(Thread::Current());
- StackHandleScope<3> hs(soa.Self());
+ StackHandleScope<2> hs(soa.Self());
ClassLinker* class_linker = dex_compilation_unit_->GetClassLinker();
- Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
- soa.Decode<mirror::ClassLoader>(dex_compilation_unit_->GetClassLoader())));
+ Handle<mirror::ClassLoader> class_loader = dex_compilation_unit_->GetClassLoader();
Handle<mirror::Class> compiling_class(hs.NewHandle(GetCompilingClass()));
// We fetch the referenced class eagerly (that is, the class pointed by in the MethodId
// at method_idx), as `CanAccessResolvedMethod` expects it be be in the dex cache.
@@ -1260,9 +1259,7 @@
static mirror::Class* GetClassFrom(CompilerDriver* driver,
const DexCompilationUnit& compilation_unit) {
ScopedObjectAccess soa(Thread::Current());
- StackHandleScope<1> hs(soa.Self());
- Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
- soa.Decode<mirror::ClassLoader>(compilation_unit.GetClassLoader())));
+ Handle<mirror::ClassLoader> class_loader = compilation_unit.GetClassLoader();
Handle<mirror::DexCache> dex_cache = compilation_unit.GetDexCache();
return driver->ResolveCompilingMethodsClass(soa, dex_cache, class_loader, &compilation_unit);
@@ -1278,10 +1275,9 @@
bool HInstructionBuilder::IsOutermostCompilingClass(dex::TypeIndex type_index) const {
ScopedObjectAccess soa(Thread::Current());
- StackHandleScope<3> hs(soa.Self());
+ StackHandleScope<2> hs(soa.Self());
Handle<mirror::DexCache> dex_cache = dex_compilation_unit_->GetDexCache();
- Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
- soa.Decode<mirror::ClassLoader>(dex_compilation_unit_->GetClassLoader())));
+ Handle<mirror::ClassLoader> class_loader = dex_compilation_unit_->GetClassLoader();
Handle<mirror::Class> cls(hs.NewHandle(compiler_driver_->ResolveClass(
soa, dex_cache, class_loader, type_index, dex_compilation_unit_)));
Handle<mirror::Class> outer_class(hs.NewHandle(GetOutermostCompilingClass()));
@@ -1317,8 +1313,7 @@
StackHandleScope<2> hs(soa.Self());
ClassLinker* class_linker = dex_compilation_unit_->GetClassLinker();
- Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
- soa.Decode<mirror::ClassLoader>(dex_compilation_unit_->GetClassLoader())));
+ Handle<mirror::ClassLoader> class_loader = dex_compilation_unit_->GetClassLoader();
Handle<mirror::Class> compiling_class(hs.NewHandle(GetCompilingClass()));
ArtField* resolved_field = class_linker->ResolveField(*dex_compilation_unit_->GetDexFile(),
@@ -1635,10 +1630,8 @@
HLoadClass* HInstructionBuilder::BuildLoadClass(dex::TypeIndex type_index, uint32_t dex_pc) {
ScopedObjectAccess soa(Thread::Current());
- StackHandleScope<2> hs(soa.Self());
const DexFile& dex_file = *dex_compilation_unit_->GetDexFile();
- Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
- soa.Decode<mirror::ClassLoader>(dex_compilation_unit_->GetClassLoader())));
+ Handle<mirror::ClassLoader> class_loader = dex_compilation_unit_->GetClassLoader();
Handle<mirror::Class> klass = handles_->NewHandle(compiler_driver_->ResolveClass(
soa, dex_compilation_unit_->GetDexCache(), class_loader, type_index, dex_compilation_unit_));
@@ -1722,17 +1715,9 @@
}
}
-bool HInstructionBuilder::NeedsAccessCheck(dex::TypeIndex type_index,
- Handle<mirror::DexCache> dex_cache,
- bool* finalizable) const {
- return !compiler_driver_->CanAccessInstantiableTypeWithoutChecks(
- dex_compilation_unit_->GetDexMethodIndex(), dex_cache, type_index, finalizable);
-}
-
bool HInstructionBuilder::NeedsAccessCheck(dex::TypeIndex type_index, bool* finalizable) const {
- ScopedObjectAccess soa(Thread::Current());
- Handle<mirror::DexCache> dex_cache = dex_compilation_unit_->GetDexCache();
- return NeedsAccessCheck(type_index, dex_cache, finalizable);
+ return !compiler_driver_->CanAccessInstantiableTypeWithoutChecks(
+ LookupReferrerClass(), LookupResolvedType(type_index, *dex_compilation_unit_), finalizable);
}
bool HInstructionBuilder::CanDecodeQuickenedInfo() const {
@@ -2772,4 +2757,18 @@
return true;
} // NOLINT(readability/fn_size)
+ObjPtr<mirror::Class> HInstructionBuilder::LookupResolvedType(
+ dex::TypeIndex type_index,
+ const DexCompilationUnit& compilation_unit) const {
+ return ClassLinker::LookupResolvedType(
+ type_index, compilation_unit.GetDexCache().Get(), compilation_unit.GetClassLoader().Get());
+}
+
+ObjPtr<mirror::Class> HInstructionBuilder::LookupReferrerClass() const {
+ // TODO: Cache the result in a Handle<mirror::Class>.
+ const DexFile::MethodId& method_id =
+ dex_compilation_unit_->GetDexFile()->GetMethodId(dex_compilation_unit_->GetDexMethodIndex());
+ return LookupResolvedType(method_id.class_idx_, *dex_compilation_unit_);
+}
+
} // namespace art
diff --git a/compiler/optimizing/instruction_builder.h b/compiler/optimizing/instruction_builder.h
index 3bb680c..e735a0c 100644
--- a/compiler/optimizing/instruction_builder.h
+++ b/compiler/optimizing/instruction_builder.h
@@ -106,11 +106,8 @@
// Returns whether the current method needs access check for the type.
// Output parameter finalizable is set to whether the type is finalizable.
- bool NeedsAccessCheck(dex::TypeIndex type_index,
- Handle<mirror::DexCache> dex_cache,
- /*out*/bool* finalizable) const
+ bool NeedsAccessCheck(dex::TypeIndex type_index, /*out*/bool* finalizable) const
REQUIRES_SHARED(Locks::mutator_lock_);
- bool NeedsAccessCheck(dex::TypeIndex type_index, /*out*/bool* finalizable) const;
template<typename T>
void Unop_12x(const Instruction& instruction, Primitive::Type type, uint32_t dex_pc);
@@ -300,6 +297,12 @@
// be found.
ArtField* ResolveField(uint16_t field_idx, bool is_static, bool is_put);
+ ObjPtr<mirror::Class> LookupResolvedType(dex::TypeIndex type_index,
+ const DexCompilationUnit& compilation_unit) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ ObjPtr<mirror::Class> LookupReferrerClass() const REQUIRES_SHARED(Locks::mutator_lock_);
+
ArenaAllocator* const arena_;
HGraph* const graph_;
VariableSizedHandleScope* handles_;
diff --git a/compiler/optimizing/instruction_simplifier_arm.cc b/compiler/optimizing/instruction_simplifier_arm.cc
index 56e4c7a..5f5e29b 100644
--- a/compiler/optimizing/instruction_simplifier_arm.cc
+++ b/compiler/optimizing/instruction_simplifier_arm.cc
@@ -15,23 +15,124 @@
*/
#include "code_generator.h"
+#include "common_arm.h"
#include "instruction_simplifier_arm.h"
#include "instruction_simplifier_shared.h"
#include "mirror/array-inl.h"
+#include "nodes.h"
namespace art {
+
+using helpers::CanFitInShifterOperand;
+using helpers::HasShifterOperand;
+
namespace arm {
-void InstructionSimplifierArmVisitor::VisitMul(HMul* instruction) {
- if (TryCombineMultiplyAccumulate(instruction, kArm)) {
+using helpers::ShifterOperandSupportsExtension;
+
+bool InstructionSimplifierArmVisitor::TryMergeIntoShifterOperand(HInstruction* use,
+ HInstruction* bitfield_op,
+ bool do_merge) {
+ DCHECK(HasShifterOperand(use, kArm));
+ DCHECK(use->IsBinaryOperation());
+ DCHECK(CanFitInShifterOperand(bitfield_op));
+ DCHECK(!bitfield_op->HasEnvironmentUses());
+
+ Primitive::Type type = use->GetType();
+ if (type != Primitive::kPrimInt && type != Primitive::kPrimLong) {
+ return false;
+ }
+
+ HInstruction* left = use->InputAt(0);
+ HInstruction* right = use->InputAt(1);
+ DCHECK(left == bitfield_op || right == bitfield_op);
+
+ if (left == right) {
+ // TODO: Handle special transformations in this situation?
+ // For example should we transform `(x << 1) + (x << 1)` into `(x << 2)`?
+ // Or should this be part of a separate transformation logic?
+ return false;
+ }
+
+ bool is_commutative = use->AsBinaryOperation()->IsCommutative();
+ HInstruction* other_input;
+ if (bitfield_op == right) {
+ other_input = left;
+ } else {
+ if (is_commutative) {
+ other_input = right;
+ } else {
+ return false;
+ }
+ }
+
+ HDataProcWithShifterOp::OpKind op_kind;
+ int shift_amount = 0;
+
+ HDataProcWithShifterOp::GetOpInfoFromInstruction(bitfield_op, &op_kind, &shift_amount);
+ shift_amount &= use->GetType() == Primitive::kPrimInt
+ ? kMaxIntShiftDistance
+ : kMaxLongShiftDistance;
+
+ if (HDataProcWithShifterOp::IsExtensionOp(op_kind)) {
+ if (!ShifterOperandSupportsExtension(use)) {
+ return false;
+ }
+ // Shift by 1 is a special case that results in the same number and type of instructions
+ // as this simplification, but potentially shorter code.
+ } else if (type == Primitive::kPrimLong && shift_amount == 1) {
+ return false;
+ }
+
+ if (do_merge) {
+ HDataProcWithShifterOp* alu_with_op =
+ new (GetGraph()->GetArena()) HDataProcWithShifterOp(use,
+ other_input,
+ bitfield_op->InputAt(0),
+ op_kind,
+ shift_amount,
+ use->GetDexPc());
+ use->GetBlock()->ReplaceAndRemoveInstructionWith(use, alu_with_op);
+ if (bitfield_op->GetUses().empty()) {
+ bitfield_op->GetBlock()->RemoveInstruction(bitfield_op);
+ }
RecordSimplification();
}
+
+ return true;
}
-void InstructionSimplifierArmVisitor::VisitOr(HOr* instruction) {
- if (TryMergeNegatedInput(instruction)) {
- RecordSimplification();
+// Merge a bitfield move instruction into its uses if it can be merged in all of them.
+bool InstructionSimplifierArmVisitor::TryMergeIntoUsersShifterOperand(HInstruction* bitfield_op) {
+ DCHECK(CanFitInShifterOperand(bitfield_op));
+
+ if (bitfield_op->HasEnvironmentUses()) {
+ return false;
}
+
+ const HUseList<HInstruction*>& uses = bitfield_op->GetUses();
+
+ // Check whether we can merge the instruction in all its users' shifter operand.
+ for (const HUseListNode<HInstruction*>& use : uses) {
+ HInstruction* user = use.GetUser();
+ if (!HasShifterOperand(user, kArm)) {
+ return false;
+ }
+ if (!CanMergeIntoShifterOperand(user, bitfield_op)) {
+ return false;
+ }
+ }
+
+ // Merge the instruction into its uses.
+ for (auto it = uses.begin(), end = uses.end(); it != end; /* ++it below */) {
+ HInstruction* user = it->GetUser();
+ // Increment `it` now because `*it` will disappear thanks to MergeIntoShifterOperand().
+ ++it;
+ bool merged = MergeIntoShifterOperand(user, bitfield_op);
+ DCHECK(merged);
+ }
+
+ return true;
}
void InstructionSimplifierArmVisitor::VisitAnd(HAnd* instruction) {
@@ -89,5 +190,49 @@
}
}
+void InstructionSimplifierArmVisitor::VisitMul(HMul* instruction) {
+ if (TryCombineMultiplyAccumulate(instruction, kArm)) {
+ RecordSimplification();
+ }
+}
+
+void InstructionSimplifierArmVisitor::VisitOr(HOr* instruction) {
+ if (TryMergeNegatedInput(instruction)) {
+ RecordSimplification();
+ }
+}
+
+void InstructionSimplifierArmVisitor::VisitShl(HShl* instruction) {
+ if (instruction->InputAt(1)->IsConstant()) {
+ TryMergeIntoUsersShifterOperand(instruction);
+ }
+}
+
+void InstructionSimplifierArmVisitor::VisitShr(HShr* instruction) {
+ if (instruction->InputAt(1)->IsConstant()) {
+ TryMergeIntoUsersShifterOperand(instruction);
+ }
+}
+
+void InstructionSimplifierArmVisitor::VisitTypeConversion(HTypeConversion* instruction) {
+ Primitive::Type result_type = instruction->GetResultType();
+ Primitive::Type input_type = instruction->GetInputType();
+
+ if (input_type == result_type) {
+ // We let the arch-independent code handle this.
+ return;
+ }
+
+ if (Primitive::IsIntegralType(result_type) && Primitive::IsIntegralType(input_type)) {
+ TryMergeIntoUsersShifterOperand(instruction);
+ }
+}
+
+void InstructionSimplifierArmVisitor::VisitUShr(HUShr* instruction) {
+ if (instruction->InputAt(1)->IsConstant()) {
+ TryMergeIntoUsersShifterOperand(instruction);
+ }
+}
+
} // namespace arm
} // namespace art
diff --git a/compiler/optimizing/instruction_simplifier_arm.h b/compiler/optimizing/instruction_simplifier_arm.h
index 9b54511..e2ed257 100644
--- a/compiler/optimizing/instruction_simplifier_arm.h
+++ b/compiler/optimizing/instruction_simplifier_arm.h
@@ -35,11 +35,41 @@
}
}
- void VisitMul(HMul* instruction) OVERRIDE;
- void VisitOr(HOr* instruction) OVERRIDE;
+ bool TryMergeIntoUsersShifterOperand(HInstruction* instruction);
+ bool TryMergeIntoShifterOperand(HInstruction* use, HInstruction* bitfield_op, bool do_merge);
+ bool CanMergeIntoShifterOperand(HInstruction* use, HInstruction* bitfield_op) {
+ return TryMergeIntoShifterOperand(use, bitfield_op, /* do_merge */ false);
+ }
+ bool MergeIntoShifterOperand(HInstruction* use, HInstruction* bitfield_op) {
+ DCHECK(CanMergeIntoShifterOperand(use, bitfield_op));
+ return TryMergeIntoShifterOperand(use, bitfield_op, /* do_merge */ true);
+ }
+
+ /**
+ * This simplifier uses a special-purpose BB visitor.
+ * (1) No need to visit Phi nodes.
+ * (2) Since statements can be removed in a "forward" fashion,
+ * the visitor should test if each statement is still there.
+ */
+ void VisitBasicBlock(HBasicBlock* block) OVERRIDE {
+ // TODO: fragile iteration, provide more robust iterators?
+ for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
+ HInstruction* instruction = it.Current();
+ if (instruction->IsInBlock()) {
+ instruction->Accept(this);
+ }
+ }
+ }
+
void VisitAnd(HAnd* instruction) OVERRIDE;
void VisitArrayGet(HArrayGet* instruction) OVERRIDE;
void VisitArraySet(HArraySet* instruction) OVERRIDE;
+ void VisitMul(HMul* instruction) OVERRIDE;
+ void VisitOr(HOr* instruction) OVERRIDE;
+ void VisitShl(HShl* instruction) OVERRIDE;
+ void VisitShr(HShr* instruction) OVERRIDE;
+ void VisitTypeConversion(HTypeConversion* instruction) OVERRIDE;
+ void VisitUShr(HUShr* instruction) OVERRIDE;
OptimizingCompilerStats* stats_;
};
diff --git a/compiler/optimizing/instruction_simplifier_arm64.cc b/compiler/optimizing/instruction_simplifier_arm64.cc
index 6d107d5..73b7b2b 100644
--- a/compiler/optimizing/instruction_simplifier_arm64.cc
+++ b/compiler/optimizing/instruction_simplifier_arm64.cc
@@ -22,16 +22,18 @@
#include "mirror/string.h"
namespace art {
-namespace arm64 {
using helpers::CanFitInShifterOperand;
using helpers::HasShifterOperand;
+
+namespace arm64 {
+
using helpers::ShifterOperandSupportsExtension;
bool InstructionSimplifierArm64Visitor::TryMergeIntoShifterOperand(HInstruction* use,
HInstruction* bitfield_op,
bool do_merge) {
- DCHECK(HasShifterOperand(use));
+ DCHECK(HasShifterOperand(use, kArm64));
DCHECK(use->IsBinaryOperation() || use->IsNeg());
DCHECK(CanFitInShifterOperand(bitfield_op));
DCHECK(!bitfield_op->HasEnvironmentUses());
@@ -72,23 +74,22 @@
}
}
- HArm64DataProcWithShifterOp::OpKind op_kind;
+ HDataProcWithShifterOp::OpKind op_kind;
int shift_amount = 0;
- HArm64DataProcWithShifterOp::GetOpInfoFromInstruction(bitfield_op, &op_kind, &shift_amount);
+ HDataProcWithShifterOp::GetOpInfoFromInstruction(bitfield_op, &op_kind, &shift_amount);
- if (HArm64DataProcWithShifterOp::IsExtensionOp(op_kind) &&
- !ShifterOperandSupportsExtension(use)) {
+ if (HDataProcWithShifterOp::IsExtensionOp(op_kind) && !ShifterOperandSupportsExtension(use)) {
return false;
}
if (do_merge) {
- HArm64DataProcWithShifterOp* alu_with_op =
- new (GetGraph()->GetArena()) HArm64DataProcWithShifterOp(use,
- other_input,
- bitfield_op->InputAt(0),
- op_kind,
- shift_amount,
- use->GetDexPc());
+ HDataProcWithShifterOp* alu_with_op =
+ new (GetGraph()->GetArena()) HDataProcWithShifterOp(use,
+ other_input,
+ bitfield_op->InputAt(0),
+ op_kind,
+ shift_amount,
+ use->GetDexPc());
use->GetBlock()->ReplaceAndRemoveInstructionWith(use, alu_with_op);
if (bitfield_op->GetUses().empty()) {
bitfield_op->GetBlock()->RemoveInstruction(bitfield_op);
@@ -112,7 +113,7 @@
// Check whether we can merge the instruction in all its users' shifter operand.
for (const HUseListNode<HInstruction*>& use : uses) {
HInstruction* user = use.GetUser();
- if (!HasShifterOperand(user)) {
+ if (!HasShifterOperand(user, kArm64)) {
return false;
}
if (!CanMergeIntoShifterOperand(user, bitfield_op)) {
diff --git a/compiler/optimizing/instruction_simplifier_arm64.h b/compiler/optimizing/instruction_simplifier_arm64.h
index d4cb1f1..65654f5 100644
--- a/compiler/optimizing/instruction_simplifier_arm64.h
+++ b/compiler/optimizing/instruction_simplifier_arm64.h
@@ -40,11 +40,11 @@
HInstruction* bitfield_op,
bool do_merge);
bool CanMergeIntoShifterOperand(HInstruction* use, HInstruction* bitfield_op) {
- return TryMergeIntoShifterOperand(use, bitfield_op, false);
+ return TryMergeIntoShifterOperand(use, bitfield_op, /* do_merge */ false);
}
bool MergeIntoShifterOperand(HInstruction* use, HInstruction* bitfield_op) {
DCHECK(CanMergeIntoShifterOperand(use, bitfield_op));
- return TryMergeIntoShifterOperand(use, bitfield_op, true);
+ return TryMergeIntoShifterOperand(use, bitfield_op, /* do_merge */ true);
}
/**
diff --git a/compiler/optimizing/instruction_simplifier_shared.h b/compiler/optimizing/instruction_simplifier_shared.h
index 56804f5..83e3ffc 100644
--- a/compiler/optimizing/instruction_simplifier_shared.h
+++ b/compiler/optimizing/instruction_simplifier_shared.h
@@ -21,6 +21,33 @@
namespace art {
+namespace helpers {
+
+inline bool CanFitInShifterOperand(HInstruction* instruction) {
+ if (instruction->IsTypeConversion()) {
+ HTypeConversion* conversion = instruction->AsTypeConversion();
+ Primitive::Type result_type = conversion->GetResultType();
+ Primitive::Type input_type = conversion->GetInputType();
+ // We don't expect to see the same type as input and result.
+ return Primitive::IsIntegralType(result_type) && Primitive::IsIntegralType(input_type) &&
+ (result_type != input_type);
+ } else {
+ return (instruction->IsShl() && instruction->AsShl()->InputAt(1)->IsIntConstant()) ||
+ (instruction->IsShr() && instruction->AsShr()->InputAt(1)->IsIntConstant()) ||
+ (instruction->IsUShr() && instruction->AsUShr()->InputAt(1)->IsIntConstant());
+ }
+}
+
+inline bool HasShifterOperand(HInstruction* instr, InstructionSet isa) {
+ // On ARM64 `neg` instructions are an alias of `sub` using the zero register
+ // as the first register input.
+ bool res = instr->IsAdd() || instr->IsAnd() || (isa == kArm64 && instr->IsNeg()) ||
+ instr->IsOr() || instr->IsSub() || instr->IsXor();
+ return res;
+}
+
+} // namespace helpers
+
bool TryCombineMultiplyAccumulate(HMul* mul, InstructionSet isa);
// For bitwise operations (And/Or/Xor) with a negated input, try to use
// a negated bitwise instruction.
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 2f258db..8a9e618 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -1362,6 +1362,7 @@
#else
#define FOR_EACH_CONCRETE_INSTRUCTION_SHARED(M) \
M(BitwiseNegatedRight, Instruction) \
+ M(DataProcWithShifterOp, Instruction) \
M(MultiplyAccumulate, Instruction) \
M(IntermediateAddress, Instruction)
#endif
@@ -1373,12 +1374,7 @@
M(ArmDexCacheArraysBase, Instruction)
#endif
-#ifndef ART_ENABLE_CODEGEN_arm64
#define FOR_EACH_CONCRETE_INSTRUCTION_ARM64(M)
-#else
-#define FOR_EACH_CONCRETE_INSTRUCTION_ARM64(M) \
- M(Arm64DataProcWithShifterOp, Instruction)
-#endif
#ifndef ART_ENABLE_CODEGEN_mips
#define FOR_EACH_CONCRETE_INSTRUCTION_MIPS(M)
@@ -6619,9 +6615,6 @@
#ifdef ART_ENABLE_CODEGEN_arm
#include "nodes_arm.h"
#endif
-#ifdef ART_ENABLE_CODEGEN_arm64
-#include "nodes_arm64.h"
-#endif
#ifdef ART_ENABLE_CODEGEN_mips
#include "nodes_mips.h"
#endif
diff --git a/compiler/optimizing/nodes_arm64.h b/compiler/optimizing/nodes_arm64.h
deleted file mode 100644
index 3f88717..0000000
--- a/compiler/optimizing/nodes_arm64.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef ART_COMPILER_OPTIMIZING_NODES_ARM64_H_
-#define ART_COMPILER_OPTIMIZING_NODES_ARM64_H_
-
-#include "nodes.h"
-
-namespace art {
-
-class HArm64DataProcWithShifterOp FINAL : public HExpression<2> {
- public:
- enum OpKind {
- kLSL, // Logical shift left.
- kLSR, // Logical shift right.
- kASR, // Arithmetic shift right.
- kUXTB, // Unsigned extend byte.
- kUXTH, // Unsigned extend half-word.
- kUXTW, // Unsigned extend word.
- kSXTB, // Signed extend byte.
- kSXTH, // Signed extend half-word.
- kSXTW, // Signed extend word.
-
- // Aliases.
- kFirstShiftOp = kLSL,
- kLastShiftOp = kASR,
- kFirstExtensionOp = kUXTB,
- kLastExtensionOp = kSXTW
- };
- HArm64DataProcWithShifterOp(HInstruction* instr,
- HInstruction* left,
- HInstruction* right,
- OpKind op,
- // The shift argument is unused if the operation
- // is an extension.
- int shift = 0,
- uint32_t dex_pc = kNoDexPc)
- : HExpression(instr->GetType(), SideEffects::None(), dex_pc),
- instr_kind_(instr->GetKind()), op_kind_(op), shift_amount_(shift) {
- DCHECK(!instr->HasSideEffects());
- SetRawInputAt(0, left);
- SetRawInputAt(1, right);
- }
-
- bool CanBeMoved() const OVERRIDE { return true; }
- bool InstructionDataEquals(const HInstruction* other_instr) const OVERRIDE {
- const HArm64DataProcWithShifterOp* other = other_instr->AsArm64DataProcWithShifterOp();
- return instr_kind_ == other->instr_kind_ &&
- op_kind_ == other->op_kind_ &&
- shift_amount_ == other->shift_amount_;
- }
-
- static bool IsShiftOp(OpKind op_kind) {
- return kFirstShiftOp <= op_kind && op_kind <= kLastShiftOp;
- }
-
- static bool IsExtensionOp(OpKind op_kind) {
- return kFirstExtensionOp <= op_kind && op_kind <= kLastExtensionOp;
- }
-
- // Find the operation kind and shift amount from a bitfield move instruction.
- static void GetOpInfoFromInstruction(HInstruction* bitfield_op,
- /*out*/OpKind* op_kind,
- /*out*/int* shift_amount);
-
- InstructionKind GetInstrKind() const { return instr_kind_; }
- OpKind GetOpKind() const { return op_kind_; }
- int GetShiftAmount() const { return shift_amount_; }
-
- DECLARE_INSTRUCTION(Arm64DataProcWithShifterOp);
-
- private:
- InstructionKind instr_kind_;
- OpKind op_kind_;
- int shift_amount_;
-
- friend std::ostream& operator<<(std::ostream& os, OpKind op);
-
- DISALLOW_COPY_AND_ASSIGN(HArm64DataProcWithShifterOp);
-};
-
-std::ostream& operator<<(std::ostream& os, const HArm64DataProcWithShifterOp::OpKind op);
-
-} // namespace art
-
-#endif // ART_COMPILER_OPTIMIZING_NODES_ARM64_H_
diff --git a/compiler/optimizing/nodes_arm64.cc b/compiler/optimizing/nodes_shared.cc
similarity index 63%
rename from compiler/optimizing/nodes_arm64.cc
rename to compiler/optimizing/nodes_shared.cc
index ac2f093..f145bf9 100644
--- a/compiler/optimizing/nodes_arm64.cc
+++ b/compiler/optimizing/nodes_shared.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -15,15 +15,15 @@
*/
#include "common_arm64.h"
-#include "nodes.h"
+#include "nodes_shared.h"
namespace art {
-using arm64::helpers::CanFitInShifterOperand;
+using helpers::CanFitInShifterOperand;
-void HArm64DataProcWithShifterOp::GetOpInfoFromInstruction(HInstruction* instruction,
- /*out*/OpKind* op_kind,
- /*out*/int* shift_amount) {
+void HDataProcWithShifterOp::GetOpInfoFromInstruction(HInstruction* instruction,
+ /*out*/OpKind* op_kind,
+ /*out*/int* shift_amount) {
DCHECK(CanFitInShifterOperand(instruction));
if (instruction->IsShl()) {
*op_kind = kLSL;
@@ -41,12 +41,11 @@
int result_size = Primitive::ComponentSize(result_type);
int input_size = Primitive::ComponentSize(input_type);
int min_size = std::min(result_size, input_size);
- // This follows the logic in
- // `InstructionCodeGeneratorARM64::VisitTypeConversion()`.
if (result_type == Primitive::kPrimInt && input_type == Primitive::kPrimLong) {
- // There is actually nothing to do. The register will be used as a W
- // register, discarding the top bits. This is represented by the default
- // encoding 'LSL 0'.
+ // There is actually nothing to do. On ARM the high register from the
+ // pair will be ignored. On ARM64 the register will be used as a W
+ // register, discarding the top bits. This is represented by the
+ // default encoding 'LSL 0'.
*op_kind = kLSL;
*shift_amount = 0;
} else if (result_type == Primitive::kPrimChar ||
@@ -64,17 +63,17 @@
}
}
-std::ostream& operator<<(std::ostream& os, const HArm64DataProcWithShifterOp::OpKind op) {
+std::ostream& operator<<(std::ostream& os, const HDataProcWithShifterOp::OpKind op) {
switch (op) {
- case HArm64DataProcWithShifterOp::kLSL: return os << "LSL";
- case HArm64DataProcWithShifterOp::kLSR: return os << "LSR";
- case HArm64DataProcWithShifterOp::kASR: return os << "ASR";
- case HArm64DataProcWithShifterOp::kUXTB: return os << "UXTB";
- case HArm64DataProcWithShifterOp::kUXTH: return os << "UXTH";
- case HArm64DataProcWithShifterOp::kUXTW: return os << "UXTW";
- case HArm64DataProcWithShifterOp::kSXTB: return os << "SXTB";
- case HArm64DataProcWithShifterOp::kSXTH: return os << "SXTH";
- case HArm64DataProcWithShifterOp::kSXTW: return os << "SXTW";
+ case HDataProcWithShifterOp::kLSL: return os << "LSL";
+ case HDataProcWithShifterOp::kLSR: return os << "LSR";
+ case HDataProcWithShifterOp::kASR: return os << "ASR";
+ case HDataProcWithShifterOp::kUXTB: return os << "UXTB";
+ case HDataProcWithShifterOp::kUXTH: return os << "UXTH";
+ case HDataProcWithShifterOp::kUXTW: return os << "UXTW";
+ case HDataProcWithShifterOp::kSXTB: return os << "SXTB";
+ case HDataProcWithShifterOp::kSXTH: return os << "SXTH";
+ case HDataProcWithShifterOp::kSXTW: return os << "SXTW";
default:
LOG(FATAL) << "Invalid OpKind " << static_cast<int>(op);
UNREACHABLE();
diff --git a/compiler/optimizing/nodes_shared.h b/compiler/optimizing/nodes_shared.h
index 814202e..c6bfbcc 100644
--- a/compiler/optimizing/nodes_shared.h
+++ b/compiler/optimizing/nodes_shared.h
@@ -150,6 +150,81 @@
DISALLOW_COPY_AND_ASSIGN(HIntermediateAddress);
};
+class HDataProcWithShifterOp FINAL : public HExpression<2> {
+ public:
+ enum OpKind {
+ kLSL, // Logical shift left.
+ kLSR, // Logical shift right.
+ kASR, // Arithmetic shift right.
+ kUXTB, // Unsigned extend byte.
+ kUXTH, // Unsigned extend half-word.
+ kUXTW, // Unsigned extend word.
+ kSXTB, // Signed extend byte.
+ kSXTH, // Signed extend half-word.
+ kSXTW, // Signed extend word.
+
+ // Aliases.
+ kFirstShiftOp = kLSL,
+ kLastShiftOp = kASR,
+ kFirstExtensionOp = kUXTB,
+ kLastExtensionOp = kSXTW
+ };
+ HDataProcWithShifterOp(HInstruction* instr,
+ HInstruction* left,
+ HInstruction* right,
+ OpKind op,
+ // The shift argument is unused if the operation
+ // is an extension.
+ int shift = 0,
+ uint32_t dex_pc = kNoDexPc)
+ : HExpression(instr->GetType(), SideEffects::None(), dex_pc),
+ instr_kind_(instr->GetKind()), op_kind_(op),
+ shift_amount_(shift & (instr->GetType() == Primitive::kPrimInt
+ ? kMaxIntShiftDistance
+ : kMaxLongShiftDistance)) {
+ DCHECK(!instr->HasSideEffects());
+ SetRawInputAt(0, left);
+ SetRawInputAt(1, right);
+ }
+
+ bool CanBeMoved() const OVERRIDE { return true; }
+ bool InstructionDataEquals(const HInstruction* other_instr) const OVERRIDE {
+ const HDataProcWithShifterOp* other = other_instr->AsDataProcWithShifterOp();
+ return instr_kind_ == other->instr_kind_ &&
+ op_kind_ == other->op_kind_ &&
+ shift_amount_ == other->shift_amount_;
+ }
+
+ static bool IsShiftOp(OpKind op_kind) {
+ return kFirstShiftOp <= op_kind && op_kind <= kLastShiftOp;
+ }
+
+ static bool IsExtensionOp(OpKind op_kind) {
+ return kFirstExtensionOp <= op_kind && op_kind <= kLastExtensionOp;
+ }
+
+ // Find the operation kind and shift amount from a bitfield move instruction.
+ static void GetOpInfoFromInstruction(HInstruction* bitfield_op,
+ /*out*/OpKind* op_kind,
+ /*out*/int* shift_amount);
+
+ InstructionKind GetInstrKind() const { return instr_kind_; }
+ OpKind GetOpKind() const { return op_kind_; }
+ int GetShiftAmount() const { return shift_amount_; }
+
+ DECLARE_INSTRUCTION(DataProcWithShifterOp);
+
+ private:
+ InstructionKind instr_kind_;
+ OpKind op_kind_;
+ int shift_amount_;
+
+ friend std::ostream& operator<<(std::ostream& os, OpKind op);
+
+ DISALLOW_COPY_AND_ASSIGN(HDataProcWithShifterOp);
+};
+
+std::ostream& operator<<(std::ostream& os, const HDataProcWithShifterOp::OpKind op);
} // namespace art
diff --git a/compiler/optimizing/optimizing_cfi_test.cc b/compiler/optimizing/optimizing_cfi_test.cc
index 0e02311..490e50c 100644
--- a/compiler/optimizing/optimizing_cfi_test.cc
+++ b/compiler/optimizing/optimizing_cfi_test.cc
@@ -24,17 +24,17 @@
#include "optimizing/code_generator.h"
#include "optimizing/optimizing_unit_test.h"
#include "utils/assembler.h"
-#ifdef ART_USE_VIXL_ARM_BACKEND
-#include "utils/arm/assembler_arm_vixl.h"
-#else
+#ifdef ART_USE_OLD_ARM_BACKEND
#include "utils/arm/assembler_thumb2.h"
+#else
+#include "utils/arm/assembler_arm_vixl.h"
#endif
#include "utils/mips/assembler_mips.h"
#include "utils/mips64/assembler_mips64.h"
#include "optimizing/optimizing_cfi_test_expected.inc"
-#ifdef ART_USE_VIXL_ARM_BACKEND
+#ifndef ART_USE_OLD_ARM_BACKEND
namespace vixl32 = vixl::aarch32;
using vixl32::r0;
@@ -196,7 +196,15 @@
expected_cfi_kThumb2_adjust,
expected_cfi_kThumb2_adjust + arraysize(expected_cfi_kThumb2_adjust));
SetUpFrame(kThumb2);
-#ifdef ART_USE_VIXL_ARM_BACKEND
+#ifdef ART_USE_OLD_ARM_BACKEND
+#define __ down_cast<arm::Thumb2Assembler*>(GetCodeGenerator()->GetAssembler())->
+ Label target;
+ __ CompareAndBranchIfZero(arm::R0, &target);
+ // Push the target out of range of CBZ.
+ for (size_t i = 0; i != 65; ++i) {
+ __ ldr(arm::R0, arm::Address(arm::R0));
+ }
+#else
#define __ down_cast<arm::ArmVIXLAssembler*>(GetCodeGenerator() \
->GetAssembler())->GetVIXLAssembler()->
vixl32::Label target;
@@ -205,14 +213,6 @@
for (size_t i = 0; i != 65; ++i) {
__ Ldr(r0, vixl32::MemOperand(r0));
}
-#else
-#define __ down_cast<arm::Thumb2Assembler*>(GetCodeGenerator()->GetAssembler())->
- Label target;
- __ CompareAndBranchIfZero(arm::R0, &target);
- // Push the target out of range of CBZ.
- for (size_t i = 0; i != 65; ++i) {
- __ ldr(arm::R0, arm::Address(arm::R0));
- }
#endif
__ Bind(&target);
#undef __
diff --git a/compiler/optimizing/optimizing_cfi_test_expected.inc b/compiler/optimizing/optimizing_cfi_test_expected.inc
index 82670c3..d84fe6c 100644
--- a/compiler/optimizing/optimizing_cfi_test_expected.inc
+++ b/compiler/optimizing/optimizing_cfi_test_expected.inc
@@ -223,15 +223,15 @@
// 0x00000040: .cfi_def_cfa_offset: 64
static constexpr uint8_t expected_asm_kThumb2_adjust[] = {
-#ifdef ART_USE_VIXL_ARM_BACKEND
- // VIXL emits an extra 2 bytes here for a 32-bit beq as there is no
- // optimistic 16-bit emit and subsequent fixup for out of reach targets
- // as with the current assembler.
- 0x60, 0xB5, 0x2D, 0xED, 0x02, 0x8A, 0x8B, 0xB0, 0x00, 0x28, 0x00, 0xF0,
- 0x41, 0x80, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68,
-#else
+#ifdef ART_USE_OLD_ARM_BACKEND
0x60, 0xB5, 0x2D, 0xED, 0x02, 0x8A, 0x8B, 0xB0, 0x00, 0x28,
0x40, 0xD0, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68,
+#else
+ // VIXL emits an extra 2 bytes here for a 32-bit beq as there is no
+ // optimistic 16-bit emit and subsequent fixup for out of reach targets
+ // as with the old assembler.
+ 0x60, 0xB5, 0x2D, 0xED, 0x02, 0x8A, 0x8B, 0xB0, 0x00, 0x28, 0x00, 0xF0,
+ 0x41, 0x80, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68,
#endif
0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68,
0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68,
@@ -247,10 +247,10 @@
};
static constexpr uint8_t expected_cfi_kThumb2_adjust[] = {
0x42, 0x0E, 0x0C, 0x85, 0x03, 0x86, 0x02, 0x8E, 0x01, 0x44, 0x0E, 0x14,
-#ifdef ART_USE_VIXL_ARM_BACKEND
- 0x05, 0x50, 0x05, 0x05, 0x51, 0x04, 0x42, 0x0E, 0x40, 0x02, 0x88, 0x0A,
-#else
+#ifdef ART_USE_OLD_ARM_BACKEND
0x05, 0x50, 0x05, 0x05, 0x51, 0x04, 0x42, 0x0E, 0x40, 0x02, 0x86, 0x0A,
+#else
+ 0x05, 0x50, 0x05, 0x05, 0x51, 0x04, 0x42, 0x0E, 0x40, 0x02, 0x88, 0x0A,
#endif
0x42, 0x0E, 0x14, 0x44, 0x0E, 0x0C, 0x06, 0x50, 0x06, 0x51, 0x42, 0x0B,
0x0E, 0x40,
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index 8638e34..f72bd6a 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -306,7 +306,7 @@
InvokeType invoke_type,
uint16_t class_def_idx,
uint32_t method_idx,
- jobject class_loader,
+ Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
Handle<mirror::DexCache> dex_cache) const OVERRIDE;
@@ -375,7 +375,7 @@
InvokeType invoke_type,
uint16_t class_def_idx,
uint32_t method_idx,
- jobject class_loader,
+ Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
Handle<mirror::DexCache> dex_cache,
ArtMethod* method,
@@ -875,7 +875,7 @@
InvokeType invoke_type,
uint16_t class_def_idx,
uint32_t method_idx,
- jobject class_loader,
+ Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
Handle<mirror::DexCache> dex_cache,
ArtMethod* method,
@@ -946,11 +946,8 @@
const uint8_t* interpreter_metadata = nullptr;
if (method == nullptr) {
ScopedObjectAccess soa(Thread::Current());
- StackHandleScope<1> hs(soa.Self());
- Handle<mirror::ClassLoader> loader(hs.NewHandle(
- soa.Decode<mirror::ClassLoader>(class_loader)));
method = compiler_driver->ResolveMethod(
- soa, dex_cache, loader, &dex_compilation_unit, method_idx, invoke_type);
+ soa, dex_cache, class_loader, &dex_compilation_unit, method_idx, invoke_type);
}
// For AOT compilation, we may not get a method, for example if its class is erroneous.
// JIT should always have a method.
@@ -959,16 +956,6 @@
graph->SetArtMethod(method);
ScopedObjectAccess soa(Thread::Current());
interpreter_metadata = method->GetQuickenedInfo(class_linker->GetImagePointerSize());
- dex::TypeIndex type_index = method->GetDeclaringClass()->GetDexTypeIndex();
-
- // Update the dex cache if the type is not in it yet. Note that under AOT,
- // the verifier must have set it, but under JIT, there's no guarantee, as we
- // don't necessarily run the verifier.
- // The compiler and the compiler driver assume the compiling class is
- // in the dex cache.
- if (dex_cache->GetResolvedType(type_index) == nullptr) {
- dex_cache->SetResolvedType(type_index, method->GetDeclaringClass());
- }
}
std::unique_ptr<CodeGenerator> codegen(
@@ -1049,7 +1036,7 @@
InvokeType invoke_type,
uint16_t class_def_idx,
uint32_t method_idx,
- jobject jclass_loader,
+ Handle<mirror::ClassLoader> jclass_loader,
const DexFile& dex_file,
Handle<mirror::DexCache> dex_cache) const {
CompilerDriver* compiler_driver = GetCompilerDriver();
@@ -1163,7 +1150,6 @@
Handle<mirror::DexCache> dex_cache(hs.NewHandle(method->GetDexCache()));
DCHECK(method->IsCompilable());
- jobject jclass_loader = class_loader.ToJObject();
const DexFile* dex_file = method->GetDexFile();
const uint16_t class_def_idx = method->GetClassDefIndex();
const DexFile::CodeItem* code_item = dex_file->GetCodeItem(method->GetCodeItemOffset());
@@ -1187,7 +1173,7 @@
invoke_type,
class_def_idx,
method_idx,
- jclass_loader,
+ class_loader,
*dex_file,
dex_cache,
method,
diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc
index c55fccc..6e332ca 100644
--- a/compiler/optimizing/reference_type_propagation.cc
+++ b/compiler/optimizing/reference_type_propagation.cc
@@ -65,11 +65,13 @@
class ReferenceTypePropagation::RTPVisitor : public HGraphDelegateVisitor {
public:
RTPVisitor(HGraph* graph,
+ Handle<mirror::ClassLoader> class_loader,
Handle<mirror::DexCache> hint_dex_cache,
HandleCache* handle_cache,
ArenaVector<HInstruction*>* worklist,
bool is_first_run)
: HGraphDelegateVisitor(graph),
+ class_loader_(class_loader),
hint_dex_cache_(hint_dex_cache),
handle_cache_(handle_cache),
worklist_(worklist),
@@ -101,6 +103,7 @@
bool is_exact);
private:
+ Handle<mirror::ClassLoader> class_loader_;
Handle<mirror::DexCache> hint_dex_cache_;
HandleCache* handle_cache_;
ArenaVector<HInstruction*>* worklist_;
@@ -108,11 +111,13 @@
};
ReferenceTypePropagation::ReferenceTypePropagation(HGraph* graph,
+ Handle<mirror::ClassLoader> class_loader,
Handle<mirror::DexCache> hint_dex_cache,
VariableSizedHandleScope* handles,
bool is_first_run,
const char* name)
: HOptimization(graph, name),
+ class_loader_(class_loader),
hint_dex_cache_(hint_dex_cache),
handle_cache_(handles),
worklist_(graph->GetArena()->Adapter(kArenaAllocReferenceTypePropagation)),
@@ -147,7 +152,12 @@
}
void ReferenceTypePropagation::Visit(HInstruction* instruction) {
- RTPVisitor visitor(graph_, hint_dex_cache_, &handle_cache_, &worklist_, is_first_run_);
+ RTPVisitor visitor(graph_,
+ class_loader_,
+ hint_dex_cache_,
+ &handle_cache_,
+ &worklist_,
+ is_first_run_);
instruction->Accept(&visitor);
}
@@ -321,7 +331,12 @@
}
void ReferenceTypePropagation::VisitBasicBlock(HBasicBlock* block) {
- RTPVisitor visitor(graph_, hint_dex_cache_, &handle_cache_, &worklist_, is_first_run_);
+ RTPVisitor visitor(graph_,
+ class_loader_,
+ hint_dex_cache_,
+ &handle_cache_,
+ &worklist_,
+ is_first_run_);
// Handle Phis first as there might be instructions in the same block who depend on them.
for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
VisitPhi(it.Current()->AsPhi());
@@ -542,8 +557,9 @@
ScopedObjectAccess soa(Thread::Current());
ObjPtr<mirror::DexCache> dex_cache = FindDexCacheWithHint(soa.Self(), dex_file, hint_dex_cache_);
- // Get type from dex cache assuming it was populated by the verifier.
- SetClassAsTypeInfo(instr, dex_cache->GetResolvedType(type_idx), is_exact);
+ ObjPtr<mirror::Class> klass =
+ ClassLinker::LookupResolvedType(type_idx, dex_cache, class_loader_.Get());
+ SetClassAsTypeInfo(instr, klass, is_exact);
}
void ReferenceTypePropagation::RTPVisitor::VisitNewInstance(HNewInstance* instr) {
@@ -556,25 +572,13 @@
SetClassAsTypeInfo(instr, instr->GetLoadClass()->GetClass().Get(), /* is_exact */ true);
}
-static mirror::Class* GetClassFromDexCache(Thread* self,
- const DexFile& dex_file,
- dex::TypeIndex type_idx,
- Handle<mirror::DexCache> hint_dex_cache)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjPtr<mirror::DexCache> dex_cache = FindDexCacheWithHint(self, dex_file, hint_dex_cache);
- // Get type from dex cache assuming it was populated by the verifier.
- return dex_cache->GetResolvedType(type_idx);
-}
-
void ReferenceTypePropagation::RTPVisitor::VisitParameterValue(HParameterValue* instr) {
// We check if the existing type is valid: the inliner may have set it.
if (instr->GetType() == Primitive::kPrimNot && !instr->GetReferenceTypeInfo().IsValid()) {
- ScopedObjectAccess soa(Thread::Current());
- mirror::Class* resolved_class = GetClassFromDexCache(soa.Self(),
- instr->GetDexFile(),
- instr->GetTypeIndex(),
- hint_dex_cache_);
- SetClassAsTypeInfo(instr, resolved_class, /* is_exact */ false);
+ UpdateReferenceTypeInfo(instr,
+ instr->GetTypeIndex(),
+ instr->GetDexFile(),
+ /* is_exact */ false);
}
}
diff --git a/compiler/optimizing/reference_type_propagation.h b/compiler/optimizing/reference_type_propagation.h
index 4663471..215e967 100644
--- a/compiler/optimizing/reference_type_propagation.h
+++ b/compiler/optimizing/reference_type_propagation.h
@@ -33,6 +33,7 @@
class ReferenceTypePropagation : public HOptimization {
public:
ReferenceTypePropagation(HGraph* graph,
+ Handle<mirror::ClassLoader> class_loader,
Handle<mirror::DexCache> hint_dex_cache,
VariableSizedHandleScope* handles,
bool is_first_run,
@@ -105,6 +106,8 @@
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 b061c87..84a4bab 100644
--- a/compiler/optimizing/reference_type_propagation_test.cc
+++ b/compiler/optimizing/reference_type_propagation_test.cc
@@ -38,6 +38,7 @@
void SetupPropagation(VariableSizedHandleScope* handles) {
graph_->InitializeInexactObjectRTI(handles);
propagation_ = new (&allocator_) ReferenceTypePropagation(graph_,
+ Handle<mirror::ClassLoader>(),
Handle<mirror::DexCache>(),
handles,
true,
diff --git a/compiler/optimizing/scheduler_arm64.cc b/compiler/optimizing/scheduler_arm64.cc
index e3701fb..558dcc4 100644
--- a/compiler/optimizing/scheduler_arm64.cc
+++ b/compiler/optimizing/scheduler_arm64.cc
@@ -31,8 +31,8 @@
last_visited_latency_ = kArm64IntegerOpLatency;
}
-void SchedulingLatencyVisitorARM64::VisitArm64DataProcWithShifterOp(
- HArm64DataProcWithShifterOp* ATTRIBUTE_UNUSED) {
+void SchedulingLatencyVisitorARM64::VisitDataProcWithShifterOp(
+ HDataProcWithShifterOp* ATTRIBUTE_UNUSED) {
last_visited_latency_ = kArm64DataProcWithShifterOpLatency;
}
diff --git a/compiler/optimizing/scheduler_arm64.h b/compiler/optimizing/scheduler_arm64.h
index 702027c..7a33720 100644
--- a/compiler/optimizing/scheduler_arm64.h
+++ b/compiler/optimizing/scheduler_arm64.h
@@ -74,7 +74,8 @@
#define FOR_EACH_SCHEDULED_SHARED_INSTRUCTION(M) \
M(BitwiseNegatedRight, unused) \
M(MultiplyAccumulate, unused) \
- M(IntermediateAddress, unused)
+ M(IntermediateAddress, unused) \
+ M(DataProcWithShifterOp, unused)
#define DECLARE_VISIT_INSTRUCTION(type, unused) \
void Visit##type(H##type* instruction) OVERRIDE;
diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc
index 487e4dd..50ab11b 100644
--- a/compiler/optimizing/ssa_builder.cc
+++ b/compiler/optimizing/ssa_builder.cc
@@ -499,7 +499,11 @@
// 4) Compute type of reference type instructions. The pass assumes that
// NullConstant has been fixed up.
- ReferenceTypePropagation(graph_, dex_cache_, handles_, /* is_first_run */ true).Run();
+ ReferenceTypePropagation(graph_,
+ class_loader_,
+ dex_cache_,
+ handles_,
+ /* is_first_run */ true).Run();
// 5) HInstructionBuilder duplicated ArrayGet instructions with ambiguous type
// (int/float or long/double) and marked ArraySets with ambiguous input type.
diff --git a/compiler/optimizing/ssa_builder.h b/compiler/optimizing/ssa_builder.h
index 45dac54..978f113 100644
--- a/compiler/optimizing/ssa_builder.h
+++ b/compiler/optimizing/ssa_builder.h
@@ -48,9 +48,11 @@
class SsaBuilder : public ValueObject {
public:
SsaBuilder(HGraph* graph,
+ Handle<mirror::ClassLoader> class_loader,
Handle<mirror::DexCache> dex_cache,
VariableSizedHandleScope* handles)
: graph_(graph),
+ class_loader_(class_loader),
dex_cache_(dex_cache),
handles_(handles),
agets_fixed_(false),
@@ -115,6 +117,7 @@
void RemoveRedundantUninitializedStrings();
HGraph* graph_;
+ Handle<mirror::ClassLoader> class_loader_;
Handle<mirror::DexCache> dex_cache_;
VariableSizedHandleScope* const handles_;
diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc
index eeae96e..4d12ad6 100644
--- a/compiler/optimizing/stack_map_stream.cc
+++ b/compiler/optimizing/stack_map_stream.cc
@@ -16,8 +16,6 @@
#include "stack_map_stream.h"
-#include <unordered_map>
-
#include "art_method-inl.h"
#include "base/stl_util.h"
#include "optimizing/optimizing_compiler.h"
@@ -526,7 +524,7 @@
size_t StackMapStream::PrepareRegisterMasks() {
register_masks_.resize(stack_maps_.size(), 0u);
- std::unordered_map<uint32_t, size_t> dedupe;
+ ArenaUnorderedMap<uint32_t, size_t> dedupe(allocator_->Adapter(kArenaAllocStackMapStream));
for (StackMapEntry& stack_map : stack_maps_) {
const size_t index = dedupe.size();
stack_map.register_mask_index = dedupe.emplace(stack_map.register_mask, index).first->second;
@@ -541,10 +539,11 @@
stack_masks_.resize(byte_entry_size * stack_maps_.size(), 0u);
// For deduplicating we store the stack masks as byte packed for simplicity. We can bit pack later
// when copying out from stack_masks_.
- std::unordered_map<MemoryRegion,
- size_t,
- FNVHash<MemoryRegion>,
- MemoryRegion::ContentEquals> dedup(stack_maps_.size());
+ ArenaUnorderedMap<MemoryRegion,
+ size_t,
+ FNVHash<MemoryRegion>,
+ MemoryRegion::ContentEquals> dedup(
+ stack_maps_.size(), allocator_->Adapter(kArenaAllocStackMapStream));
for (StackMapEntry& stack_map : stack_maps_) {
size_t index = dedup.size();
MemoryRegion stack_mask(stack_masks_.data() + index * byte_entry_size, byte_entry_size);
diff --git a/compiler/utils/swap_space.cc b/compiler/utils/swap_space.cc
index 1a8f567..a1eb08e 100644
--- a/compiler/utils/swap_space.cc
+++ b/compiler/utils/swap_space.cc
@@ -36,17 +36,17 @@
static void DumpFreeMap(const FreeBySizeSet& free_by_size) {
size_t last_size = static_cast<size_t>(-1);
for (const auto& entry : free_by_size) {
- if (last_size != entry.first) {
- last_size = entry.first;
+ if (last_size != entry.size) {
+ last_size = entry.size;
LOG(INFO) << "Size " << last_size;
}
- LOG(INFO) << " 0x" << std::hex << entry.second->Start()
- << " size=" << std::dec << entry.second->size;
+ LOG(INFO) << " 0x" << std::hex << entry.free_by_start_entry->Start()
+ << " size=" << std::dec << entry.free_by_start_entry->size;
}
}
void SwapSpace::RemoveChunk(FreeBySizeSet::const_iterator free_by_size_pos) {
- auto free_by_start_pos = free_by_size_pos->second;
+ auto free_by_start_pos = free_by_size_pos->free_by_start_entry;
free_by_size_.erase(free_by_size_pos);
free_by_start_.erase(free_by_start_pos);
}
@@ -89,7 +89,7 @@
// Calculate over free_by_size.
size_t sum1 = 0;
for (const auto& entry : free_by_size) {
- sum1 += entry.second->size;
+ sum1 += entry.free_by_start_entry->size;
}
// Calculate over free_by_start.
@@ -110,27 +110,52 @@
// Check the free list for something that fits.
// TODO: Smarter implementation. Global biggest chunk, ...
- SpaceChunk old_chunk;
auto it = free_by_start_.empty()
? free_by_size_.end()
: free_by_size_.lower_bound(FreeBySizeEntry { size, free_by_start_.begin() });
if (it != free_by_size_.end()) {
- old_chunk = *it->second;
- RemoveChunk(it);
+ auto entry = it->free_by_start_entry;
+ SpaceChunk old_chunk = *entry;
+ if (old_chunk.size == size) {
+ RemoveChunk(it);
+ } else {
+ // Try to avoid deallocating and allocating the std::set<> nodes.
+ // This would be much simpler if we could use replace() from Boost.Bimap.
+
+ // The free_by_start_ map contains disjoint intervals ordered by the `ptr`.
+ // Shrinking the interval does not affect the ordering.
+ it->free_by_start_entry->ptr += size;
+ it->free_by_start_entry->size -= size;
+
+ // The free_by_size_ map is ordered by the `size` and then `free_by_start_entry->ptr`.
+ // Adjusting the `ptr` above does not change that ordering but decreasing `size` can
+ // push the node before the previous node(s).
+ if (it == free_by_size_.begin()) {
+ it->size -= size;
+ } else {
+ auto prev = it;
+ --prev;
+ FreeBySizeEntry new_value(old_chunk.size - size, entry);
+ if (free_by_size_.key_comp()(*prev, new_value)) {
+ it->size -= size;
+ } else {
+ // Changing in place would break the std::set<> ordering, we need to remove and insert.
+ free_by_size_.erase(it);
+ free_by_size_.insert(new_value);
+ }
+ }
+ }
+ return old_chunk.ptr;
} else {
// Not a big enough free chunk, need to increase file size.
- old_chunk = NewFileChunk(size);
+ SpaceChunk new_chunk = NewFileChunk(size);
+ if (new_chunk.size != size) {
+ // Insert the remainder.
+ SpaceChunk remainder = { new_chunk.ptr + size, new_chunk.size - size };
+ InsertChunk(remainder);
+ }
+ return new_chunk.ptr;
}
-
- void* ret = old_chunk.ptr;
-
- if (old_chunk.size != size) {
- // Insert the remainder.
- SpaceChunk new_chunk = { old_chunk.ptr + size, old_chunk.size - size };
- InsertChunk(new_chunk);
- }
-
- return ret;
}
SwapSpace::SpaceChunk SwapSpace::NewFileChunk(size_t min_size) {
diff --git a/compiler/utils/swap_space.h b/compiler/utils/swap_space.h
index 9600907..c286b82 100644
--- a/compiler/utils/swap_space.h
+++ b/compiler/utils/swap_space.h
@@ -45,8 +45,10 @@
private:
// Chunk of space.
struct SpaceChunk {
- uint8_t* ptr;
- size_t size;
+ // We need mutable members as we keep these objects in a std::set<> (providing only const
+ // access) but we modify these members while carefully preserving the std::set<> ordering.
+ mutable uint8_t* ptr;
+ mutable size_t size;
uintptr_t Start() const {
return reinterpret_cast<uintptr_t>(ptr);
@@ -66,13 +68,21 @@
typedef std::set<SpaceChunk, SortChunkByPtr> FreeByStartSet;
// Map size to an iterator to free_by_start_'s entry.
- typedef std::pair<size_t, FreeByStartSet::const_iterator> FreeBySizeEntry;
+ struct FreeBySizeEntry {
+ FreeBySizeEntry(size_t sz, FreeByStartSet::const_iterator entry)
+ : size(sz), free_by_start_entry(entry) { }
+
+ // We need mutable members as we keep these objects in a std::set<> (providing only const
+ // access) but we modify these members while carefully preserving the std::set<> ordering.
+ mutable size_t size;
+ mutable FreeByStartSet::const_iterator free_by_start_entry;
+ };
struct FreeBySizeComparator {
bool operator()(const FreeBySizeEntry& lhs, const FreeBySizeEntry& rhs) {
- if (lhs.first != rhs.first) {
- return lhs.first < rhs.first;
+ if (lhs.size != rhs.size) {
+ return lhs.size < rhs.size;
} else {
- return lhs.second->Start() < rhs.second->Start();
+ return lhs.free_by_start_entry->Start() < rhs.free_by_start_entry->Start();
}
}
};
diff --git a/compiler/utils/x86/assembler_x86.cc b/compiler/utils/x86/assembler_x86.cc
index 5a466e1..6eab302 100644
--- a/compiler/utils/x86/assembler_x86.cc
+++ b/compiler/utils/x86/assembler_x86.cc
@@ -642,39 +642,6 @@
}
-void X86Assembler::psrldq(XmmRegister reg, const Immediate& shift_count) {
- DCHECK(shift_count.is_uint8());
-
- AssemblerBuffer::EnsureCapacity ensured(&buffer_);
- EmitUint8(0x66);
- EmitUint8(0x0F);
- EmitUint8(0x73);
- EmitXmmRegisterOperand(3, reg);
- EmitUint8(shift_count.value());
-}
-
-
-void X86Assembler::psrlq(XmmRegister reg, const Immediate& shift_count) {
- DCHECK(shift_count.is_uint8());
-
- AssemblerBuffer::EnsureCapacity ensured(&buffer_);
- EmitUint8(0x66);
- EmitUint8(0x0F);
- EmitUint8(0x73);
- EmitXmmRegisterOperand(2, reg);
- EmitUint8(shift_count.value());
-}
-
-
-void X86Assembler::punpckldq(XmmRegister dst, XmmRegister src) {
- AssemblerBuffer::EnsureCapacity ensured(&buffer_);
- EmitUint8(0x66);
- EmitUint8(0x0F);
- EmitUint8(0x62);
- EmitXmmRegisterOperand(dst, src);
-}
-
-
void X86Assembler::addsd(XmmRegister dst, XmmRegister src) {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(0xF2);
@@ -828,6 +795,51 @@
}
+void X86Assembler::paddb(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0xFC);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::psubb(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0xF8);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::paddw(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0xFD);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::psubw(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0xF9);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::pmullw(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0xD5);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
void X86Assembler::paddd(XmmRegister dst, XmmRegister src) {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(0x66);
@@ -856,6 +868,24 @@
}
+void X86Assembler::paddq(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0xD4);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::psubq(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0xFB);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
void X86Assembler::cvtsi2ss(XmmRegister dst, Register src) {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(0xF3);
@@ -1186,6 +1216,141 @@
}
+void X86Assembler::punpcklbw(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0x60);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::punpcklwd(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0x61);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::punpckldq(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0x62);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::punpcklqdq(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0x6C);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::psllw(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0x71);
+ EmitXmmRegisterOperand(6, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::pslld(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0x72);
+ EmitXmmRegisterOperand(6, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::psllq(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0x73);
+ EmitXmmRegisterOperand(6, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::psraw(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0x71);
+ EmitXmmRegisterOperand(4, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::psrad(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0x72);
+ EmitXmmRegisterOperand(4, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::psrlw(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0x71);
+ EmitXmmRegisterOperand(2, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::psrld(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0x72);
+ EmitXmmRegisterOperand(2, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::psrlq(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0x73);
+ EmitXmmRegisterOperand(2, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::psrldq(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0x73);
+ EmitXmmRegisterOperand(3, reg);
+ EmitUint8(shift_count.value());
+}
+
+
void X86Assembler::fldl(const Address& src) {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(0xDD);
diff --git a/compiler/utils/x86/assembler_x86.h b/compiler/utils/x86/assembler_x86.h
index 4343e2e..2999599 100644
--- a/compiler/utils/x86/assembler_x86.h
+++ b/compiler/utils/x86/assembler_x86.h
@@ -408,14 +408,9 @@
void movsd(const Address& dst, XmmRegister src);
void movsd(XmmRegister dst, XmmRegister src);
- void psrlq(XmmRegister reg, const Immediate& shift_count);
- void punpckldq(XmmRegister dst, XmmRegister src);
-
void movhpd(XmmRegister dst, const Address& src);
void movhpd(const Address& dst, XmmRegister src);
- void psrldq(XmmRegister reg, const Immediate& shift_count);
-
void addsd(XmmRegister dst, XmmRegister src);
void addsd(XmmRegister dst, const Address& src);
void subsd(XmmRegister dst, XmmRegister src);
@@ -436,10 +431,20 @@
void movdqa(const Address& dst, XmmRegister src); // store aligned
void movdqu(const Address& dst, XmmRegister src); // store unaligned
- void paddd(XmmRegister dst, XmmRegister src); // no addr variant (for now)
+ void paddb(XmmRegister dst, XmmRegister src); // no addr variant (for now)
+ void psubb(XmmRegister dst, XmmRegister src);
+
+ void paddw(XmmRegister dst, XmmRegister src);
+ void psubw(XmmRegister dst, XmmRegister src);
+ void pmullw(XmmRegister dst, XmmRegister src);
+
+ void paddd(XmmRegister dst, XmmRegister src);
void psubd(XmmRegister dst, XmmRegister src);
void pmulld(XmmRegister dst, XmmRegister src);
+ void paddq(XmmRegister dst, XmmRegister src);
+ void psubq(XmmRegister dst, XmmRegister src);
+
void cvtsi2ss(XmmRegister dst, Register src);
void cvtsi2sd(XmmRegister dst, Register src);
@@ -489,6 +494,24 @@
void shufps(XmmRegister dst, XmmRegister src, const Immediate& imm);
void pshufd(XmmRegister dst, XmmRegister src, const Immediate& imm);
+ void punpcklbw(XmmRegister dst, XmmRegister src);
+ void punpcklwd(XmmRegister dst, XmmRegister src);
+ void punpckldq(XmmRegister dst, XmmRegister src);
+ void punpcklqdq(XmmRegister dst, XmmRegister src);
+
+ void psllw(XmmRegister reg, const Immediate& shift_count);
+ void pslld(XmmRegister reg, const Immediate& shift_count);
+ void psllq(XmmRegister reg, const Immediate& shift_count);
+
+ void psraw(XmmRegister reg, const Immediate& shift_count);
+ void psrad(XmmRegister reg, const Immediate& shift_count);
+ // no psraq
+
+ void psrlw(XmmRegister reg, const Immediate& shift_count);
+ void psrld(XmmRegister reg, const Immediate& shift_count);
+ void psrlq(XmmRegister reg, const Immediate& shift_count);
+ void psrldq(XmmRegister reg, const Immediate& shift_count);
+
void flds(const Address& src);
void fstps(const Address& dst);
void fsts(const Address& dst);
diff --git a/compiler/utils/x86/assembler_x86_test.cc b/compiler/utils/x86/assembler_x86_test.cc
index c6ab893..a74bea2 100644
--- a/compiler/utils/x86/assembler_x86_test.cc
+++ b/compiler/utils/x86/assembler_x86_test.cc
@@ -122,18 +122,6 @@
DriverStr(expected, "movntl");
}
-TEST_F(AssemblerX86Test, psrlq) {
- GetAssembler()->psrlq(x86::XMM0, CreateImmediate(32));
- const char* expected = "psrlq $0x20, %xmm0\n";
- DriverStr(expected, "psrlq");
-}
-
-TEST_F(AssemblerX86Test, punpckldq) {
- GetAssembler()->punpckldq(x86::XMM0, x86::XMM1);
- const char* expected = "punpckldq %xmm1, %xmm0\n";
- DriverStr(expected, "punpckldq");
-}
-
TEST_F(AssemblerX86Test, LoadLongConstant) {
GetAssembler()->LoadLongConstant(x86::XMM0, 51);
const char* expected =
@@ -521,6 +509,26 @@
DriverStr(RepeatFF(&x86::X86Assembler::divpd, "divpd %{reg2}, %{reg1}"), "divpd");
}
+TEST_F(AssemblerX86Test, PAddB) {
+ DriverStr(RepeatFF(&x86::X86Assembler::paddb, "paddb %{reg2}, %{reg1}"), "paddb");
+}
+
+TEST_F(AssemblerX86Test, PSubB) {
+ DriverStr(RepeatFF(&x86::X86Assembler::psubb, "psubb %{reg2}, %{reg1}"), "psubb");
+}
+
+TEST_F(AssemblerX86Test, PAddW) {
+ DriverStr(RepeatFF(&x86::X86Assembler::paddw, "paddw %{reg2}, %{reg1}"), "paddw");
+}
+
+TEST_F(AssemblerX86Test, PSubW) {
+ DriverStr(RepeatFF(&x86::X86Assembler::psubw, "psubw %{reg2}, %{reg1}"), "psubw");
+}
+
+TEST_F(AssemblerX86Test, PMullW) {
+ DriverStr(RepeatFF(&x86::X86Assembler::pmullw, "pmullw %{reg2}, %{reg1}"), "pmullw");
+}
+
TEST_F(AssemblerX86Test, PAddD) {
DriverStr(RepeatFF(&x86::X86Assembler::paddd, "paddd %{reg2}, %{reg1}"), "paddd");
}
@@ -533,6 +541,14 @@
DriverStr(RepeatFF(&x86::X86Assembler::pmulld, "pmulld %{reg2}, %{reg1}"), "pmulld");
}
+TEST_F(AssemblerX86Test, PAddQ) {
+ DriverStr(RepeatFF(&x86::X86Assembler::paddq, "paddq %{reg2}, %{reg1}"), "paddq");
+}
+
+TEST_F(AssemblerX86Test, PSubQ) {
+ DriverStr(RepeatFF(&x86::X86Assembler::psubq, "psubq %{reg2}, %{reg1}"), "psubq");
+}
+
TEST_F(AssemblerX86Test, XorPD) {
DriverStr(RepeatFF(&x86::X86Assembler::xorpd, "xorpd %{reg2}, %{reg1}"), "xorpd");
}
@@ -581,6 +597,67 @@
DriverStr(RepeatFFI(&x86::X86Assembler::pshufd, 1, "pshufd ${imm}, %{reg2}, %{reg1}"), "pshufd");
}
+TEST_F(AssemblerX86Test, Punpcklbw) {
+ DriverStr(RepeatFF(&x86::X86Assembler::punpcklbw, "punpcklbw %{reg2}, %{reg1}"), "punpcklbw");
+}
+
+TEST_F(AssemblerX86Test, Punpcklwd) {
+ DriverStr(RepeatFF(&x86::X86Assembler::punpcklwd, "punpcklwd %{reg2}, %{reg1}"), "punpcklwd");
+}
+
+TEST_F(AssemblerX86Test, Punpckldq) {
+ DriverStr(RepeatFF(&x86::X86Assembler::punpckldq, "punpckldq %{reg2}, %{reg1}"), "punpckldq");
+}
+
+TEST_F(AssemblerX86Test, Punpcklqdq) {
+ DriverStr(RepeatFF(&x86::X86Assembler::punpcklqdq, "punpcklqdq %{reg2}, %{reg1}"), "punpcklqdq");
+}
+
+TEST_F(AssemblerX86Test, psllw) {
+ GetAssembler()->psllw(x86::XMM0, CreateImmediate(16));
+ DriverStr("psllw $0x10, %xmm0\n", "psllwi");
+}
+
+TEST_F(AssemblerX86Test, pslld) {
+ GetAssembler()->pslld(x86::XMM0, CreateImmediate(16));
+ DriverStr("pslld $0x10, %xmm0\n", "pslldi");
+}
+
+TEST_F(AssemblerX86Test, psllq) {
+ GetAssembler()->psllq(x86::XMM0, CreateImmediate(16));
+ DriverStr("psllq $0x10, %xmm0\n", "psllqi");
+}
+
+TEST_F(AssemblerX86Test, psraw) {
+ GetAssembler()->psraw(x86::XMM0, CreateImmediate(16));
+ DriverStr("psraw $0x10, %xmm0\n", "psrawi");
+}
+
+TEST_F(AssemblerX86Test, psrad) {
+ GetAssembler()->psrad(x86::XMM0, CreateImmediate(16));
+ DriverStr("psrad $0x10, %xmm0\n", "psradi");
+}
+
+TEST_F(AssemblerX86Test, psrlw) {
+ GetAssembler()->psrlw(x86::XMM0, CreateImmediate(16));
+ DriverStr("psrlw $0x10, %xmm0\n", "psrlwi");
+}
+
+TEST_F(AssemblerX86Test, psrld) {
+ GetAssembler()->psrld(x86::XMM0, CreateImmediate(16));
+ DriverStr("psrld $0x10, %xmm0\n", "psrldi");
+}
+
+TEST_F(AssemblerX86Test, psrlq) {
+ GetAssembler()->psrlq(x86::XMM0, CreateImmediate(16));
+ DriverStr("psrlq $0x10, %xmm0\n", "psrlqi");
+}
+
+TEST_F(AssemblerX86Test, psrldq) {
+ GetAssembler()->psrldq(x86::XMM0, CreateImmediate(16));
+ DriverStr("psrldq $0x10, %xmm0\n", "psrldqi");
+}
+
/////////////////
// Near labels //
/////////////////
diff --git a/compiler/utils/x86_64/assembler_x86_64.cc b/compiler/utils/x86_64/assembler_x86_64.cc
index b41be80..458204a 100644
--- a/compiler/utils/x86_64/assembler_x86_64.cc
+++ b/compiler/utils/x86_64/assembler_x86_64.cc
@@ -882,6 +882,56 @@
}
+void X86_64Assembler::paddb(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex32(dst, src);
+ EmitUint8(0x0F);
+ EmitUint8(0xFC);
+ EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::psubb(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex32(dst, src);
+ EmitUint8(0x0F);
+ EmitUint8(0xF8);
+ EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::paddw(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex32(dst, src);
+ EmitUint8(0x0F);
+ EmitUint8(0xFD);
+ EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::psubw(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex32(dst, src);
+ EmitUint8(0x0F);
+ EmitUint8(0xF9);
+ EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::pmullw(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex32(dst, src);
+ EmitUint8(0x0F);
+ EmitUint8(0xD5);
+ EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
void X86_64Assembler::paddd(XmmRegister dst, XmmRegister src) {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(0x66);
@@ -913,6 +963,26 @@
}
+void X86_64Assembler::paddq(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex32(dst, src);
+ EmitUint8(0x0F);
+ EmitUint8(0xD4);
+ EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::psubq(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex32(dst, src);
+ EmitUint8(0x0F);
+ EmitUint8(0xFB);
+ EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
void X86_64Assembler::cvtsi2ss(XmmRegister dst, CpuRegister src) {
cvtsi2ss(dst, src, false);
}
@@ -1354,6 +1424,142 @@
}
+void X86_64Assembler::punpcklbw(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex32(dst, src);
+ EmitUint8(0x0F);
+ EmitUint8(0x60);
+ EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::punpcklwd(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex32(dst, src);
+ EmitUint8(0x0F);
+ EmitUint8(0x61);
+ EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::punpckldq(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex32(dst, src);
+ EmitUint8(0x0F);
+ EmitUint8(0x62);
+ EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::punpcklqdq(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex32(dst, src);
+ EmitUint8(0x0F);
+ EmitUint8(0x6C);
+ EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::psllw(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+ EmitUint8(0x0F);
+ EmitUint8(0x71);
+ EmitXmmRegisterOperand(6, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86_64Assembler::pslld(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+ EmitUint8(0x0F);
+ EmitUint8(0x72);
+ EmitXmmRegisterOperand(6, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86_64Assembler::psllq(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+ EmitUint8(0x0F);
+ EmitUint8(0x73);
+ EmitXmmRegisterOperand(6, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86_64Assembler::psraw(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+ EmitUint8(0x0F);
+ EmitUint8(0x71);
+ EmitXmmRegisterOperand(4, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86_64Assembler::psrad(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+ EmitUint8(0x0F);
+ EmitUint8(0x72);
+ EmitXmmRegisterOperand(4, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86_64Assembler::psrlw(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+ EmitUint8(0x0F);
+ EmitUint8(0x71);
+ EmitXmmRegisterOperand(2, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86_64Assembler::psrld(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+ EmitUint8(0x0F);
+ EmitUint8(0x72);
+ EmitXmmRegisterOperand(2, reg);
+ EmitUint8(shift_count.value());
+}
+
+
+void X86_64Assembler::psrlq(XmmRegister reg, const Immediate& shift_count) {
+ DCHECK(shift_count.is_uint8());
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+ EmitUint8(0x0F);
+ EmitUint8(0x73);
+ EmitXmmRegisterOperand(2, reg);
+ EmitUint8(shift_count.value());
+}
+
+
void X86_64Assembler::fldl(const Address& src) {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(0xDD);
diff --git a/compiler/utils/x86_64/assembler_x86_64.h b/compiler/utils/x86_64/assembler_x86_64.h
index 43ea12a..0dc11d8 100644
--- a/compiler/utils/x86_64/assembler_x86_64.h
+++ b/compiler/utils/x86_64/assembler_x86_64.h
@@ -452,10 +452,20 @@
void movdqa(const Address& dst, XmmRegister src); // store aligned
void movdqu(const Address& dst, XmmRegister src); // store unaligned
- void paddd(XmmRegister dst, XmmRegister src); // no addr variant (for now)
+ void paddb(XmmRegister dst, XmmRegister src); // no addr variant (for now)
+ void psubb(XmmRegister dst, XmmRegister src);
+
+ void paddw(XmmRegister dst, XmmRegister src);
+ void psubw(XmmRegister dst, XmmRegister src);
+ void pmullw(XmmRegister dst, XmmRegister src);
+
+ void paddd(XmmRegister dst, XmmRegister src);
void psubd(XmmRegister dst, XmmRegister src);
void pmulld(XmmRegister dst, XmmRegister src);
+ void paddq(XmmRegister dst, XmmRegister src);
+ void psubq(XmmRegister dst, XmmRegister src);
+
void cvtsi2ss(XmmRegister dst, CpuRegister src); // Note: this is the r/m32 version.
void cvtsi2ss(XmmRegister dst, CpuRegister src, bool is64bit);
void cvtsi2ss(XmmRegister dst, const Address& src, bool is64bit);
@@ -512,6 +522,23 @@
void shufps(XmmRegister dst, XmmRegister src, const Immediate& imm);
void pshufd(XmmRegister dst, XmmRegister src, const Immediate& imm);
+ void punpcklbw(XmmRegister dst, XmmRegister src);
+ void punpcklwd(XmmRegister dst, XmmRegister src);
+ void punpckldq(XmmRegister dst, XmmRegister src);
+ void punpcklqdq(XmmRegister dst, XmmRegister src);
+
+ void psllw(XmmRegister reg, const Immediate& shift_count);
+ void pslld(XmmRegister reg, const Immediate& shift_count);
+ void psllq(XmmRegister reg, const Immediate& shift_count);
+
+ void psraw(XmmRegister reg, const Immediate& shift_count);
+ void psrad(XmmRegister reg, const Immediate& shift_count);
+ // no psraq
+
+ void psrlw(XmmRegister reg, const Immediate& shift_count);
+ void psrld(XmmRegister reg, const Immediate& shift_count);
+ void psrlq(XmmRegister reg, const Immediate& shift_count);
+
void flds(const Address& src);
void fstps(const Address& dst);
void fsts(const Address& dst);
diff --git a/compiler/utils/x86_64/assembler_x86_64_test.cc b/compiler/utils/x86_64/assembler_x86_64_test.cc
index aeb1911..fe94497 100644
--- a/compiler/utils/x86_64/assembler_x86_64_test.cc
+++ b/compiler/utils/x86_64/assembler_x86_64_test.cc
@@ -1128,6 +1128,26 @@
DriverStr(RepeatFF(&x86_64::X86_64Assembler::divpd, "divpd %{reg2}, %{reg1}"), "divpd");
}
+TEST_F(AssemblerX86_64Test, Paddb) {
+ DriverStr(RepeatFF(&x86_64::X86_64Assembler::paddb, "paddb %{reg2}, %{reg1}"), "paddb");
+}
+
+TEST_F(AssemblerX86_64Test, Psubb) {
+ DriverStr(RepeatFF(&x86_64::X86_64Assembler::psubb, "psubb %{reg2}, %{reg1}"), "psubb");
+}
+
+TEST_F(AssemblerX86_64Test, Paddw) {
+ DriverStr(RepeatFF(&x86_64::X86_64Assembler::paddw, "paddw %{reg2}, %{reg1}"), "paddw");
+}
+
+TEST_F(AssemblerX86_64Test, Psubw) {
+ DriverStr(RepeatFF(&x86_64::X86_64Assembler::psubw, "psubw %{reg2}, %{reg1}"), "psubw");
+}
+
+TEST_F(AssemblerX86_64Test, Pmullw) {
+ DriverStr(RepeatFF(&x86_64::X86_64Assembler::pmullw, "pmullw %{reg2}, %{reg1}"), "pmullw");
+}
+
TEST_F(AssemblerX86_64Test, Paddd) {
DriverStr(RepeatFF(&x86_64::X86_64Assembler::paddd, "paddd %{reg2}, %{reg1}"), "paddd");
}
@@ -1140,6 +1160,14 @@
DriverStr(RepeatFF(&x86_64::X86_64Assembler::pmulld, "pmulld %{reg2}, %{reg1}"), "pmulld");
}
+TEST_F(AssemblerX86_64Test, Paddq) {
+ DriverStr(RepeatFF(&x86_64::X86_64Assembler::paddq, "paddq %{reg2}, %{reg1}"), "paddq");
+}
+
+TEST_F(AssemblerX86_64Test, Psubq) {
+ DriverStr(RepeatFF(&x86_64::X86_64Assembler::psubq, "psubq %{reg2}, %{reg1}"), "psubq");
+}
+
TEST_F(AssemblerX86_64Test, Cvtsi2ss) {
DriverStr(RepeatFr(&x86_64::X86_64Assembler::cvtsi2ss, "cvtsi2ss %{reg2}, %{reg1}"), "cvtsi2ss");
}
@@ -1261,6 +1289,78 @@
DriverStr(RepeatFFI(&x86_64::X86_64Assembler::pshufd, 1, "pshufd ${imm}, %{reg2}, %{reg1}"), "pshufd");
}
+TEST_F(AssemblerX86_64Test, Punpcklbw) {
+ DriverStr(RepeatFF(&x86_64::X86_64Assembler::punpcklbw, "punpcklbw %{reg2}, %{reg1}"), "punpcklbw");
+}
+
+TEST_F(AssemblerX86_64Test, Punpcklwd) {
+ DriverStr(RepeatFF(&x86_64::X86_64Assembler::punpcklwd, "punpcklwd %{reg2}, %{reg1}"), "punpcklwd");
+}
+
+TEST_F(AssemblerX86_64Test, Punpckldq) {
+ DriverStr(RepeatFF(&x86_64::X86_64Assembler::punpckldq, "punpckldq %{reg2}, %{reg1}"), "punpckldq");
+}
+
+TEST_F(AssemblerX86_64Test, Punpcklqdq) {
+ DriverStr(RepeatFF(&x86_64::X86_64Assembler::punpcklqdq, "punpcklqdq %{reg2}, %{reg1}"), "punpcklqdq");
+}
+
+TEST_F(AssemblerX86_64Test, Psllw) {
+ GetAssembler()->psllw(x86_64::XmmRegister(x86_64::XMM0), x86_64::Immediate(1));
+ GetAssembler()->psllw(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+ DriverStr("psllw $1, %xmm0\n"
+ "psllw $2, %xmm15\n", "psllwi");
+}
+
+TEST_F(AssemblerX86_64Test, Pslld) {
+ GetAssembler()->pslld(x86_64::XmmRegister(x86_64::XMM0), x86_64::Immediate(1));
+ GetAssembler()->pslld(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+ DriverStr("pslld $1, %xmm0\n"
+ "pslld $2, %xmm15\n", "pslldi");
+}
+
+TEST_F(AssemblerX86_64Test, Psllq) {
+ GetAssembler()->psllq(x86_64::XmmRegister(x86_64::XMM0), x86_64::Immediate(1));
+ GetAssembler()->psllq(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+ DriverStr("psllq $1, %xmm0\n"
+ "psllq $2, %xmm15\n", "psllqi");
+}
+
+TEST_F(AssemblerX86_64Test, Psraw) {
+ GetAssembler()->psraw(x86_64::XmmRegister(x86_64::XMM0), x86_64::Immediate(1));
+ GetAssembler()->psraw(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+ DriverStr("psraw $1, %xmm0\n"
+ "psraw $2, %xmm15\n", "psrawi");
+}
+
+TEST_F(AssemblerX86_64Test, Psrad) {
+ GetAssembler()->psrad(x86_64::XmmRegister(x86_64::XMM0), x86_64::Immediate(1));
+ GetAssembler()->psrad(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+ DriverStr("psrad $1, %xmm0\n"
+ "psrad $2, %xmm15\n", "psradi");
+}
+
+TEST_F(AssemblerX86_64Test, Psrlw) {
+ GetAssembler()->psrlw(x86_64::XmmRegister(x86_64::XMM0), x86_64::Immediate(1));
+ GetAssembler()->psrlw(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+ DriverStr("psrlw $1, %xmm0\n"
+ "psrlw $2, %xmm15\n", "psrlwi");
+}
+
+TEST_F(AssemblerX86_64Test, Psrld) {
+ GetAssembler()->psrld(x86_64::XmmRegister(x86_64::XMM0), x86_64::Immediate(1));
+ GetAssembler()->psrld(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+ DriverStr("psrld $1, %xmm0\n"
+ "psrld $2, %xmm15\n", "pslldi");
+}
+
+TEST_F(AssemblerX86_64Test, Psrlq) {
+ GetAssembler()->psrlq(x86_64::XmmRegister(x86_64::XMM0), x86_64::Immediate(1));
+ GetAssembler()->psrlq(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+ DriverStr("psrlq $1, %xmm0\n"
+ "psrlq $2, %xmm15\n", "pslrqi");
+}
+
TEST_F(AssemblerX86_64Test, UcomissAddress) {
GetAssembler()->ucomiss(x86_64::XmmRegister(x86_64::XMM0), x86_64::Address(
x86_64::CpuRegister(x86_64::RDI), x86_64::CpuRegister(x86_64::RBX), x86_64::TIMES_4, 12));
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 026a567..be75628 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -424,7 +424,13 @@
shutting_down_(false) {
const char* reason = "dex2oat watch dog thread startup";
CHECK_WATCH_DOG_PTHREAD_CALL(pthread_mutex_init, (&mutex_, nullptr), reason);
- CHECK_WATCH_DOG_PTHREAD_CALL(pthread_cond_init, (&cond_, nullptr), reason);
+#ifndef __APPLE__
+ pthread_condattr_t condattr;
+ CHECK_WATCH_DOG_PTHREAD_CALL(pthread_condattr_init, (&condattr), reason);
+ CHECK_WATCH_DOG_PTHREAD_CALL(pthread_condattr_setclock, (&condattr, CLOCK_MONOTONIC), reason);
+ CHECK_WATCH_DOG_PTHREAD_CALL(pthread_cond_init, (&cond_, &condattr), reason);
+ CHECK_WATCH_DOG_PTHREAD_CALL(pthread_condattr_destroy, (&condattr), reason);
+#endif
CHECK_WATCH_DOG_PTHREAD_CALL(pthread_attr_init, (&attr_), reason);
CHECK_WATCH_DOG_PTHREAD_CALL(pthread_create, (&pthread_, &attr_, &CallBack, this), reason);
CHECK_WATCH_DOG_PTHREAD_CALL(pthread_attr_destroy, (&attr_), reason);
@@ -482,7 +488,11 @@
void Wait() {
timespec timeout_ts;
+#if defined(__APPLE__)
InitTimeSpec(true, CLOCK_REALTIME, timeout_in_milliseconds_, 0, &timeout_ts);
+#else
+ InitTimeSpec(true, CLOCK_MONOTONIC, timeout_in_milliseconds_, 0, &timeout_ts);
+#endif
const char* reason = "dex2oat watch dog thread waiting";
CHECK_WATCH_DOG_PTHREAD_CALL(pthread_mutex_lock, (&mutex_), reason);
while (!shutting_down_) {
@@ -1541,10 +1551,10 @@
std::unique_ptr<MemMap> opened_dex_files_map;
std::vector<std::unique_ptr<const DexFile>> opened_dex_files;
// No need to verify the dex file for:
- // 1) kSpeedProfile, since it includes dexlayout, which does the verification.
+ // 1) Dexlayout since it does the verification. It also may not pass the verification since
+ // we don't update the dex checksum.
// 2) when we have a vdex file, which means it was already verified.
- bool verify = compiler_options_->GetCompilerFilter() != CompilerFilter::kSpeedProfile &&
- (input_vdex_file_ == nullptr);
+ const bool verify = !DoDexLayoutOptimizations() && (input_vdex_file_ == nullptr);
if (!oat_writers_[i]->WriteAndOpenDexFiles(
kIsVdexEnabled ? vdex_files_[i].get() : oat_files_[i].get(),
rodata_.back(),
@@ -2094,12 +2104,20 @@
return is_host_;
}
- bool UseProfileGuidedCompilation() const {
- return CompilerFilter::DependsOnProfile(compiler_options_->GetCompilerFilter());
+ bool UseProfile() const {
+ return profile_file_fd_ != -1 || !profile_file_.empty();
+ }
+
+ bool DoProfileGuidedOptimizations() const {
+ return UseProfile() && compiler_options_->GetCompilerFilter() != CompilerFilter::kVerifyProfile;
+ }
+
+ bool DoDexLayoutOptimizations() const {
+ return DoProfileGuidedOptimizations();
}
bool LoadProfile() {
- DCHECK(UseProfileGuidedCompilation());
+ DCHECK(UseProfile());
profile_compilation_info_.reset(new ProfileCompilationInfo());
ScopedFlock flock;
@@ -2356,7 +2374,7 @@
compiler_options_.get(),
oat_file.get()));
elf_writers_.back()->Start();
- bool do_dexlayout = compiler_options_->GetCompilerFilter() == CompilerFilter::kSpeedProfile;
+ const bool do_dexlayout = DoDexLayoutOptimizations();
oat_writers_.emplace_back(new OatWriter(
IsBootImage(), timings_, do_dexlayout ? profile_compilation_info_.get() : nullptr));
}
@@ -2873,7 +2891,7 @@
// If needed, process profile information for profile guided compilation.
// This operation involves I/O.
- if (dex2oat->UseProfileGuidedCompilation()) {
+ if (dex2oat->UseProfile()) {
if (!dex2oat->LoadProfile()) {
LOG(ERROR) << "Failed to process profile file";
return EXIT_FAILURE;
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index 6881f75..2c0b125 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -554,6 +554,12 @@
RunTest(CompilerFilter::kSpeed, true, { "--very-large-app-threshold=100" });
}
+// Regressin test for b/35665292.
+TEST_F(Dex2oatVeryLargeTest, SpeedProfileNoProfile) {
+ // Test that dex2oat doesn't crash with speed-profile but no input profile.
+ RunTest(CompilerFilter::kSpeedProfile, false);
+}
+
class Dex2oatLayoutTest : public Dex2oatTest {
protected:
void CheckFilter(CompilerFilter::Filter input ATTRIBUTE_UNUSED,
diff --git a/dexlayout/dex_ir.cc b/dexlayout/dex_ir.cc
index 2d9bbfd..609068f 100644
--- a/dexlayout/dex_ir.cc
+++ b/dexlayout/dex_ir.cc
@@ -616,6 +616,7 @@
for (std::unique_ptr<const CatchHandler>& existing_handlers : *handler_list) {
if (handler_off == existing_handlers->GetListOffset()) {
handlers = existing_handlers.get();
+ break;
}
}
if (handlers == nullptr) {
@@ -634,7 +635,51 @@
TryItem* try_item = new TryItem(start_addr, insn_count, handlers);
tries->push_back(std::unique_ptr<const TryItem>(try_item));
}
+ // Manually walk catch handlers list and add any missing handlers unreferenced by try items.
+ const uint8_t* handlers_base = DexFile::GetCatchHandlerData(disk_code_item, 0);
+ const uint8_t* handlers_data = handlers_base;
+ uint32_t handlers_size = DecodeUnsignedLeb128(&handlers_data);
+ while (handlers_size > handler_list->size()) {
+ bool already_added = false;
+ uint16_t handler_off = handlers_data - handlers_base;
+ for (std::unique_ptr<const CatchHandler>& existing_handlers : *handler_list) {
+ if (handler_off == existing_handlers->GetListOffset()) {
+ already_added = true;
+ break;
+ }
+ }
+ int32_t size = DecodeSignedLeb128(&handlers_data);
+ bool has_catch_all = size < 0;
+ if (has_catch_all) {
+ size = -size;
+ }
+ if (already_added == true) {
+ for (int32_t i = 0; i < size; i++) {
+ DecodeUnsignedLeb128(&handlers_data);
+ DecodeUnsignedLeb128(&handlers_data);
+ }
+ if (has_catch_all) {
+ DecodeUnsignedLeb128(&handlers_data);
+ }
+ continue;
+ }
+ TypeAddrPairVector* addr_pairs = new TypeAddrPairVector();
+ for (int32_t i = 0; i < size; i++) {
+ const TypeId* type_id = GetTypeIdOrNullPtr(DecodeUnsignedLeb128(&handlers_data));
+ uint32_t addr = DecodeUnsignedLeb128(&handlers_data);
+ addr_pairs->push_back(
+ std::unique_ptr<const TypeAddrPair>(new TypeAddrPair(type_id, addr)));
+ }
+ if (has_catch_all) {
+ uint32_t addr = DecodeUnsignedLeb128(&handlers_data);
+ addr_pairs->push_back(
+ std::unique_ptr<const TypeAddrPair>(new TypeAddrPair(nullptr, addr)));
+ }
+ const CatchHandler* handler = new CatchHandler(has_catch_all, handler_off, addr_pairs);
+ handler_list->push_back(std::unique_ptr<const CatchHandler>(handler));
+ }
}
+
uint32_t size = GetCodeItemSize(dex_file, disk_code_item);
CodeItem* code_item = new CodeItem(
registers_size, ins_size, outs_size, debug_info, insns_size, insns, tries, handler_list);
diff --git a/dexlayout/dexlayout_test.cc b/dexlayout/dexlayout_test.cc
index da1e1d2..9881e28 100644
--- a/dexlayout/dexlayout_test.cc
+++ b/dexlayout/dexlayout_test.cc
@@ -41,7 +41,7 @@
"AAAAdQEAAAAQAAABAAAAjAEAAA==";
static const char kDexFileLayoutInputProfile[] =
- "cHJvADAwMgABAAsAAAABAPUpbf5jbGFzc2VzLmRleAEA";
+ "cHJvADAwMwABCwABAAAAAAD1KW3+Y2xhc3Nlcy5kZXgBAA==";
static const char kDexFileLayoutExpectedOutputDex[] =
"ZGV4CjAzNQD1KW3+B8NAB0f2A/ZVIBJ0aHrGIqcpVTAUAgAAcAAAAHhWNBIAAAAAAAAAAIwBAAAH"
@@ -55,19 +55,87 @@
"qAAAAAYAAAACAAAAwAAAAAEgAAACAAAAAAEAAAIgAAAHAAAAMAEAAAMgAAACAAAAaQEAAAAgAAAC"
"AAAAdQEAAAAQAAABAAAAjAEAAA==";
-static void WriteFileBase64(const char* base64, const char* location) {
+// Dex file with multiple code items that have the same debug_info_off_. Constructed by a modified
+// dexlayout on XandY.
+static const char kDexFileDuplicateOffset[] =
+ "ZGV4CjAzNwAQfXfPCB8qCxo7MqdFhmHZQwCv8+udHD8MBAAAcAAAAHhWNBIAAAAAAAAAAFQDAAAT"
+ "AAAAcAAAAAgAAAC8AAAAAQAAANwAAAABAAAA6AAAAAUAAADwAAAAAwAAABgBAACUAgAAeAEAABQC"
+ "AAAeAgAAJgIAACsCAAAyAgAANwIAAFsCAAB7AgAAngIAALICAAC1AgAAvQIAAMUCAADIAgAA1QIA"
+ "AOkCAADvAgAA9QIAAPwCAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAkAAAAHAAAA"
+ "AAAAAAIAAQASAAAAAAAAAAEAAAABAAAAAQAAAAIAAAAAAAAAAgAAAAEAAAAGAAAAAQAAAAAAAAAA"
+ "AAAABgAAAAAAAAAKAAAAAAAAACsDAAAAAAAAAQAAAAAAAAAGAAAAAAAAAAsAAAD0AQAANQMAAAAA"
+ "AAACAAAAAAAAAAAAAAAAAAAACwAAAAQCAAA/AwAAAAAAAAIAAAAUAwAAGgMAAAEAAAAjAwAAAQAB"
+ "AAEAAAAFAAAABAAAAHAQBAAAAA4AAQABAAEAAAAFAAAABAAAAHAQBAAAAA4AAQAAAAEAAAAFAAAA"
+ "CAAAACIAAQBwEAEAAABpAAAADgABAAEAAQAAAAUAAAAEAAAAcBAAAAAADgB4AQAAAAAAAAAAAAAA"
+ "AAAAhAEAAAAAAAAAAAAAAAAAAAg8Y2xpbml0PgAGPGluaXQ+AANMWDsABUxZJFo7AANMWTsAIkxk"
+ "YWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5l"
+ "ckNsYXNzOwAhTGRhbHZpay9hbm5vdGF0aW9uL01lbWJlckNsYXNzZXM7ABJMamF2YS9sYW5nL09i"
+ "amVjdDsAAVYABlguamF2YQAGWS5qYXZhAAFaAAthY2Nlc3NGbGFncwASZW1pdHRlcjogamFjay00"
+ "LjI1AARuYW1lAAR0aGlzAAV2YWx1ZQABegARAAcOABMABw4AEgAHDnYAEQAHDgACAwERGAICBAIN"
+ "BAgPFwwCBQERHAEYAQAAAQAAgIAEjAMAAAEAAYCABKQDAQACAAAIAoiABLwDAYCABNwDAAAADwAA"
+ "AAAAAAABAAAAAAAAAAEAAAATAAAAcAAAAAIAAAAIAAAAvAAAAAMAAAABAAAA3AAAAAQAAAABAAAA"
+ "6AAAAAUAAAAFAAAA8AAAAAYAAAADAAAAGAEAAAMQAAACAAAAeAEAAAEgAAAEAAAAjAEAAAYgAAAC"
+ "AAAA9AEAAAIgAAATAAAAFAIAAAMgAAAEAAAA/wIAAAQgAAADAAAAFAMAAAAgAAADAAAAKwMAAAAQ"
+ "AAABAAAAVAMAAA==";
+
+// Dex file with null value for annotations_off in the annotation_set_ref_list.
+// Constructed by building a dex file with annotations and hex editing.
+static const char kNullSetRefListElementInputDex[] =
+ "ZGV4CjAzNQB1iA+7ZwgkF+7E6ZesYFc2lRAR3qnRAanwAwAAcAAAAHhWNBIAAAAAAAAAACADAAAS"
+ "AAAAcAAAAAgAAAC4AAAAAwAAANgAAAABAAAA/AAAAAQAAAAEAQAAAgAAACQBAACMAgAAZAEAAOgB"
+ "AADwAQAAAAIAAAMCAAAQAgAAIAIAADQCAABIAgAAawIAAI0CAAC1AgAAyAIAANECAADUAgAA2QIA"
+ "ANwCAADjAgAA6QIAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAMAAAAAgAAAAMAAAAAAAAA"
+ "DAAAAAcAAAAAAAAADQAAAAcAAADgAQAABgAGAAsAAAAAAAEAAAAAAAAAAgAOAAAAAQAAABAAAAAC"
+ "AAEAAAAAAAAAAAAAAAAAAgAAAAAAAAABAAAAsAEAAAgDAAAAAAAAAQAAAAEmAAACAAAA2AEAAAoA"
+ "AADIAQAAFgMAAAAAAAACAAAAAAAAAHwBAAABAAAA/AIAAAAAAAABAAAAAgMAAAEAAQABAAAA8AIA"
+ "AAQAAABwEAMAAAAOAAIAAgAAAAAA9QIAAAEAAAAOAAAAAAAAAAAAAAAAAAAAAQAAAAEAAABkAQAA"
+ "cAEAAAAAAAAAAAAAAAAAAAEAAAAEAAAAAgAAAAMAAwAGPGluaXQ+AA5Bbm5vQ2xhc3MuamF2YQAB"
+ "TAALTEFubm9DbGFzczsADkxNeUFubm90YXRpb247ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZh"
+ "L2xhbmcvU3RyaW5nOwAhTGphdmEvbGFuZy9hbm5vdGF0aW9uL0Fubm90YXRpb247ACBMamF2YS9s"
+ "YW5nL2Fubm90YXRpb24vUmV0ZW50aW9uOwAmTGphdmEvbGFuZy9hbm5vdGF0aW9uL1JldGVudGlv"
+ "blBvbGljeTsAEU15QW5ub3RhdGlvbi5qYXZhAAdSVU5USU1FAAFWAANWTEwAAWEABWFOYW1lAARu"
+ "YW1lAAV2YWx1ZQABAAcOAAICAAAHDgABBQERGwABAQEQFw8AAAIAAICABIQDAQmcAwAAAAECgQgA"
+ "AAARAAAAAAAAAAEAAAAAAAAAAQAAABIAAABwAAAAAgAAAAgAAAC4AAAAAwAAAAMAAADYAAAABAAA"
+ "AAEAAAD8AAAABQAAAAQAAAAEAQAABgAAAAIAAAAkAQAAAhAAAAEAAABkAQAAAxAAAAMAAABwAQAA"
+ "ASAAAAIAAACEAQAABiAAAAIAAACwAQAAARAAAAIAAADYAQAAAiAAABIAAADoAQAAAyAAAAIAAADw"
+ "AgAABCAAAAIAAAD8AgAAACAAAAIAAAAIAwAAABAAAAEAAAAgAwAA";
+
+// Dex file with catch handler unreferenced by try blocks.
+// Constructed by building a dex file with try/catch blocks and hex editing.
+static const char kUnreferencedCatchHandlerInputDex[] =
+ "ZGV4CjAzNQD+exd52Y0f9nY5x5GmInXq5nXrO6Kl2RV4AwAAcAAAAHhWNBIAAAAAAAAAANgCAAAS"
+ "AAAAcAAAAAgAAAC4AAAAAwAAANgAAAABAAAA/AAAAAQAAAAEAQAAAQAAACQBAAA0AgAARAEAANYB"
+ "AADeAQAA5gEAAO4BAAAAAgAADwIAACYCAAA9AgAAUQIAAGUCAAB5AgAAfwIAAIUCAACIAgAAjAIA"
+ "AKECAACnAgAArAIAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAwAAAAOAAAADAAAAAYAAAAAAAAA"
+ "DQAAAAYAAADIAQAADQAAAAYAAADQAQAABQABABAAAAAAAAAAAAAAAAAAAgAPAAAAAQABABEAAAAD"
+ "AAAAAAAAAAAAAAABAAAAAwAAAAAAAAADAAAAAAAAAMgCAAAAAAAAAQABAAEAAAC1AgAABAAAAHAQ"
+ "AwAAAA4AAwABAAIAAgC6AgAAIQAAAGIAAAAaAQoAbiACABAAYgAAABoBCwBuIAIAEAAOAA0AYgAA"
+ "ABoBAQBuIAIAEAAo8A0AYgAAABoBAgBuIAIAEAAo7gAAAAAAAAcAAQAHAAAABwABAAIBAg8BAhgA"
+ "AQAAAAQAAAABAAAABwAGPGluaXQ+AAZDYXRjaDEABkNhdGNoMgAQSGFuZGxlclRlc3QuamF2YQAN"
+ "TEhhbmRsZXJUZXN0OwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABVMamF2YS9sYW5nL0V4Y2VwdGlv"
+ "bjsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5nL1N5"
+ "c3RlbTsABFRyeTEABFRyeTIAAVYAAlZMABNbTGphdmEvbGFuZy9TdHJpbmc7AARtYWluAANvdXQA"
+ "B3ByaW50bG4AAQAHDgAEAQAHDn17AncdHoseAAAAAgAAgYAExAIBCdwCAAANAAAAAAAAAAEAAAAA"
+ "AAAAAQAAABIAAABwAAAAAgAAAAgAAAC4AAAAAwAAAAMAAADYAAAABAAAAAEAAAD8AAAABQAAAAQA"
+ "AAAEAQAABgAAAAEAAAAkAQAAASAAAAIAAABEAQAAARAAAAIAAADIAQAAAiAAABIAAADWAQAAAyAA"
+ "AAIAAAC1AgAAACAAAAEAAADIAgAAABAAAAEAAADYAgAA";
+
+static void WriteBase64ToFile(const char* base64, File* file) {
// Decode base64.
CHECK(base64 != nullptr);
size_t length;
std::unique_ptr<uint8_t[]> bytes(DecodeBase64(base64, &length));
- CHECK(bytes.get() != nullptr);
-
- // Write to provided file.
- std::unique_ptr<File> file(OS::CreateEmptyFile(location));
- CHECK(file.get() != nullptr);
+ CHECK(bytes != nullptr);
if (!file->WriteFully(bytes.get(), length)) {
PLOG(FATAL) << "Failed to write base64 as file";
}
+}
+
+static void WriteFileBase64(const char* base64, const char* location) {
+ // Write to provided file.
+ std::unique_ptr<File> file(OS::CreateEmptyFile(location));
+ CHECK(file != nullptr);
+ WriteBase64ToFile(base64, file.get());
if (file->FlushCloseOrErase() != 0) {
PLOG(FATAL) << "Could not flush and close test file.";
}
@@ -171,7 +239,7 @@
EXPECT_TRUE(OS::FileExists(dexlayout.c_str())) << dexlayout << " should be a valid file path";
std::vector<std::string> dexlayout_exec_argv =
- { dexlayout, "-w", tmp_dir, "-o", tmp_name, "-p", profile_file, dex_file };
+ { dexlayout, "-w", tmp_dir, "-o", tmp_name, "-p", profile_file, dex_file };
if (!::art::Exec(dexlayout_exec_argv, error_msg)) {
return false;
}
@@ -188,6 +256,40 @@
}
return true;
}
+
+ // Runs UnreferencedCatchHandlerTest.
+ bool UnreferencedCatchHandlerExec(std::string* error_msg) {
+ ScratchFile tmp_file;
+ std::string tmp_name = tmp_file.GetFilename();
+ size_t tmp_last_slash = tmp_name.rfind("/");
+ std::string tmp_dir = tmp_name.substr(0, tmp_last_slash + 1);
+
+ // Write inputs and expected outputs.
+ std::string input_dex = tmp_dir + "classes.dex";
+ WriteFileBase64(kUnreferencedCatchHandlerInputDex, input_dex.c_str());
+ std::string output_dex = tmp_dir + "classes.dex.new";
+
+ std::string dexlayout = GetTestAndroidRoot() + "/bin/dexlayout";
+ EXPECT_TRUE(OS::FileExists(dexlayout.c_str())) << dexlayout << " should be a valid file path";
+
+ std::vector<std::string> dexlayout_exec_argv =
+ { dexlayout, "-w", tmp_dir, "-o", "/dev/null", input_dex };
+ if (!::art::Exec(dexlayout_exec_argv, error_msg)) {
+ return false;
+ }
+
+ // Diff input and output. They should be the same.
+ std::vector<std::string> diff_exec_argv = { "/usr/bin/diff", input_dex, output_dex };
+ if (!::art::Exec(diff_exec_argv, error_msg)) {
+ return false;
+ }
+
+ std::vector<std::string> rm_exec_argv = { "/bin/rm", input_dex, output_dex };
+ if (!::art::Exec(rm_exec_argv, error_msg)) {
+ return false;
+ }
+ return true;
+ }
};
@@ -212,4 +314,48 @@
ASSERT_TRUE(DexFileLayoutExec(&error_msg)) << error_msg;
}
+TEST_F(DexLayoutTest, DuplicateOffset) {
+ ScratchFile temp;
+ WriteBase64ToFile(kDexFileDuplicateOffset, temp.GetFile());
+ EXPECT_EQ(temp.GetFile()->Flush(), 0);
+ std::string dexlayout = GetTestAndroidRoot() + "/bin/dexlayout";
+ EXPECT_TRUE(OS::FileExists(dexlayout.c_str())) << dexlayout << " should be a valid file path";
+ std::vector<std::string> dexlayout_exec_argv = {
+ dexlayout,
+ "-a",
+ "-i",
+ "-o",
+ "/dev/null",
+ temp.GetFilename()};
+ std::string error_msg;
+ const bool result = ::art::Exec(dexlayout_exec_argv, &error_msg);
+ EXPECT_TRUE(result);
+ if (!result) {
+ LOG(ERROR) << "Error " << error_msg;
+ }
+}
+
+TEST_F(DexLayoutTest, NullSetRefListElement) {
+ ScratchFile temp;
+ WriteBase64ToFile(kNullSetRefListElementInputDex, temp.GetFile());
+ EXPECT_EQ(temp.GetFile()->Flush(), 0);
+ std::string dexlayout = GetTestAndroidRoot() + "/bin/dexlayout";
+ EXPECT_TRUE(OS::FileExists(dexlayout.c_str())) << dexlayout << " should be a valid file path";
+ std::vector<std::string> dexlayout_exec_argv =
+ { dexlayout, "-o", "/dev/null", temp.GetFilename() };
+ std::string error_msg;
+ const bool result = ::art::Exec(dexlayout_exec_argv, &error_msg);
+ EXPECT_TRUE(result);
+ if (!result) {
+ LOG(ERROR) << "Error " << error_msg;
+ }
+}
+
+TEST_F(DexLayoutTest, UnreferencedCatchHandler) {
+ // Disable test on target.
+ TEST_DISABLED_FOR_TARGET();
+ std::string error_msg;
+ ASSERT_TRUE(UnreferencedCatchHandlerExec(&error_msg)) << error_msg;
+}
+
} // namespace art
diff --git a/oatdump/Android.mk b/oatdump/Android.mk
index d80df70..aa07d24 100644
--- a/oatdump/Android.mk
+++ b/oatdump/Android.mk
@@ -41,7 +41,7 @@
.PHONY: dump-oat-core-target-$(TARGET_ARCH)
ifeq ($(ART_BUILD_TARGET),true)
-dump-oat-core-target-$(TARGET_ARCH): $(TARGET_CORE_IMAGE_default_no-pic_$(ART_PHONY_TEST_TARGET_SUFFIX)) $(OATDUMP)
+dump-oat-core-target-$(TARGET_ARCH): $(TARGET_CORE_IMAGE_default_$(ART_PHONY_TEST_TARGET_SUFFIX)) $(OATDUMP)
$(OATDUMP) --image=$(TARGET_CORE_IMG_LOCATION) \
--output=$(ART_DUMP_OAT_PATH)/core.target.$(TARGET_ARCH).oatdump.txt --instruction-set=$(TARGET_ARCH)
@echo Output in $(ART_DUMP_OAT_PATH)/core.target.$(TARGET_ARCH).oatdump.txt
@@ -50,7 +50,7 @@
ifdef TARGET_2ND_ARCH
.PHONY: dump-oat-core-target-$(TARGET_2ND_ARCH)
ifeq ($(ART_BUILD_TARGET),true)
-dump-oat-core-target-$(TARGET_2ND_ARCH): $(TARGET_CORE_IMAGE_default_no-pic_$(2ND_ART_PHONY_TEST_TARGET_SUFFIX)) $(OATDUMP)
+dump-oat-core-target-$(TARGET_2ND_ARCH): $(TARGET_CORE_IMAGE_default_$(2ND_ART_PHONY_TEST_TARGET_SUFFIX)) $(OATDUMP)
$(OATDUMP) --image=$(TARGET_CORE_IMG_LOCATION) \
--output=$(ART_DUMP_OAT_PATH)/core.target.$(TARGET_2ND_ARCH).oatdump.txt --instruction-set=$(TARGET_2ND_ARCH)
@echo Output in $(ART_DUMP_OAT_PATH)/core.target.$(TARGET_2ND_ARCH).oatdump.txt
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index a0919a1..becb827 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -2244,9 +2244,14 @@
ScopedIndentation indent2(&state->vios_);
auto* resolved_types = dex_cache->GetResolvedTypes();
for (size_t i = 0; i < num_types; ++i) {
- auto* elem = resolved_types[i].Read();
+ auto pair = resolved_types[i].load(std::memory_order_relaxed);
size_t run = 0;
- for (size_t j = i + 1; j != num_types && elem == resolved_types[j].Read(); ++j) {
+ for (size_t j = i + 1; j != num_types; ++j) {
+ auto other_pair = resolved_types[j].load(std::memory_order_relaxed);
+ if (pair.index != other_pair.index ||
+ pair.object.Read() != other_pair.object.Read()) {
+ break;
+ }
++run;
}
if (run == 0) {
@@ -2256,12 +2261,13 @@
i = i + run;
}
std::string msg;
+ auto* elem = pair.object.Read();
if (elem == nullptr) {
msg = "null";
} else {
msg = elem->PrettyClass();
}
- os << StringPrintf("%p %s\n", elem, msg.c_str());
+ os << StringPrintf("%p %u %s\n", elem, pair.index, msg.c_str());
}
}
}
diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc
index b9be5f2..18a6670 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -54,48 +54,6 @@
namespace art {
-static bool LocationToFilename(const std::string& location, InstructionSet isa,
- std::string* filename) {
- bool has_system = false;
- bool has_cache = false;
- // image_location = /system/framework/boot.art
- // system_image_filename = /system/framework/<image_isa>/boot.art
- std::string system_filename(GetSystemImageFilename(location.c_str(), isa));
- if (OS::FileExists(system_filename.c_str())) {
- has_system = true;
- }
-
- bool have_android_data = false;
- bool dalvik_cache_exists = false;
- bool is_global_cache = false;
- std::string dalvik_cache;
- GetDalvikCache(GetInstructionSetString(isa), false, &dalvik_cache,
- &have_android_data, &dalvik_cache_exists, &is_global_cache);
-
- std::string cache_filename;
- if (have_android_data && dalvik_cache_exists) {
- // Always set output location even if it does not exist,
- // so that the caller knows where to create the image.
- //
- // image_location = /system/framework/boot.art
- // *image_filename = /data/dalvik-cache/<image_isa>/boot.art
- std::string error_msg;
- if (GetDalvikCacheFilename(location.c_str(), dalvik_cache.c_str(),
- &cache_filename, &error_msg)) {
- has_cache = true;
- }
- }
- if (has_system) {
- *filename = system_filename;
- return true;
- } else if (has_cache) {
- *filename = cache_filename;
- return true;
- } else {
- return false;
- }
-}
-
static const OatHeader* GetOatHeader(const ElfFile* elf_file) {
uint64_t off = 0;
if (!elf_file->GetSectionOffsetAndSize(".rodata", &off, nullptr)) {
@@ -106,28 +64,10 @@
return oat_header;
}
-// This function takes an elf file and reads the current patch delta value
-// encoded in its oat header value
-static bool ReadOatPatchDelta(const ElfFile* elf_file, off_t* delta, std::string* error_msg) {
- const OatHeader* oat_header = GetOatHeader(elf_file);
- if (oat_header == nullptr) {
- *error_msg = "Unable to get oat header from elf file.";
- return false;
- }
- if (!oat_header->IsValid()) {
- *error_msg = "Elf file has an invalid oat header";
- return false;
- }
- *delta = oat_header->GetImagePatchDelta();
- return true;
-}
-
-static File* CreateOrOpen(const char* name, bool* created) {
+static File* CreateOrOpen(const char* name) {
if (OS::FileExists(name)) {
- *created = false;
return OS::OpenFileReadWrite(name);
} else {
- *created = true;
std::unique_ptr<File> f(OS::CreateEmptyFile(name));
if (f.get() != nullptr) {
if (fchmod(f->Fd(), 0644) != 0) {
@@ -206,12 +146,11 @@
Thread::Current()->TransitionFromRunnableToSuspended(kNative);
ScopedObjectAccess soa(Thread::Current());
- t.NewTiming("Image and oat Patching setup");
+ t.NewTiming("Image Patching setup");
std::vector<gc::space::ImageSpace*> spaces = Runtime::Current()->GetHeap()->GetBootImageSpaces();
std::map<gc::space::ImageSpace*, std::unique_ptr<File>> space_to_file_map;
std::map<gc::space::ImageSpace*, std::unique_ptr<MemMap>> space_to_memmap_map;
std::map<gc::space::ImageSpace*, PatchOat> space_to_patchoat_map;
- std::map<gc::space::ImageSpace*, bool> space_to_skip_patching_map;
for (size_t i = 0; i < spaces.size(); ++i) {
gc::space::ImageSpace* space = spaces[i];
@@ -255,8 +194,7 @@
space_to_memmap_map.emplace(space, std::move(image));
}
- // Do a first pass over the image spaces. Symlink PIC oat and vdex files, and
- // prepare PatchOat instances for the rest.
+ // Symlink PIC oat and vdex files and patch the image spaces in memory.
for (size_t i = 0; i < spaces.size(); ++i) {
gc::space::ImageSpace* space = spaces[i];
std::string input_image_filename = space->GetImageFilename();
@@ -277,14 +215,17 @@
return false;
}
- bool skip_patching_oat = false;
MaybePic is_oat_pic = IsOatPic(elf.get());
if (is_oat_pic >= ERROR_FIRST) {
// Error logged by IsOatPic
return false;
- } else if (is_oat_pic == PIC) {
- // Do not need to do ELF-file patching. Create a symlink and skip the ELF patching.
+ } else if (is_oat_pic == NOT_PIC) {
+ LOG(ERROR) << "patchoat cannot be used on non-PIC oat file: " << input_oat_file->GetPath();
+ return false;
+ } else {
+ CHECK(is_oat_pic == PIC);
+ // Create a symlink.
std::string converted_image_filename = space->GetImageLocation();
std::replace(converted_image_filename.begin() + 1, converted_image_filename.end(), '/', '@');
std::string output_image_filename = output_directory +
@@ -296,23 +237,16 @@
ImageHeader::GetOatLocationFromImageLocation(output_image_filename);
if (!ReplaceOatFileWithSymlink(input_oat_file->GetPath(),
- output_oat_filename,
- false,
- true) ||
+ output_oat_filename) ||
!SymlinkFile(input_vdex_filename, output_vdex_filename)) {
// Errors already logged by above call.
return false;
}
- // Don't patch the OAT, since we just symlinked it. Image still needs patching.
- skip_patching_oat = true;
- } else {
- CHECK(is_oat_pic == NOT_PIC);
}
PatchOat& p = space_to_patchoat_map.emplace(space,
PatchOat(
isa,
- elf.release(),
space_to_memmap_map.find(space)->second.get(),
space->GetLiveBitmap(),
space->GetMemMap(),
@@ -320,36 +254,24 @@
&space_to_memmap_map,
timings)).first->second;
- t.NewTiming("Patching files");
- if (!skip_patching_oat && !p.PatchElf()) {
- LOG(ERROR) << "Failed to patch oat file " << input_oat_file->GetPath();
- return false;
- }
+ t.NewTiming("Patching image");
if (!p.PatchImage(i == 0)) {
LOG(ERROR) << "Failed to patch image file " << input_image_filename;
return false;
}
-
- space_to_skip_patching_map.emplace(space, skip_patching_oat);
}
- // Do a second pass over the image spaces. Patch image files, non-PIC oat files
- // and symlink their corresponding vdex files.
+ // Write the patched image spaces.
for (size_t i = 0; i < spaces.size(); ++i) {
gc::space::ImageSpace* space = spaces[i];
- std::string input_image_filename = space->GetImageFilename();
- std::string input_vdex_filename =
- ImageHeader::GetVdexLocationFromImageLocation(input_image_filename);
- t.NewTiming("Writing files");
+ t.NewTiming("Writing image");
std::string converted_image_filename = space->GetImageLocation();
std::replace(converted_image_filename.begin() + 1, converted_image_filename.end(), '/', '@');
std::string output_image_filename = output_directory +
(android::base::StartsWith(converted_image_filename, "/") ? "" : "/") +
converted_image_filename;
- bool new_oat_out;
- std::unique_ptr<File>
- output_image_file(CreateOrOpen(output_image_filename.c_str(), &new_oat_out));
+ std::unique_ptr<File> output_image_file(CreateOrOpen(output_image_filename.c_str()));
if (output_image_file.get() == nullptr) {
LOG(ERROR) << "Failed to open output image file at " << output_image_filename;
return false;
@@ -362,48 +284,10 @@
if (!success) {
return false;
}
-
- bool skip_patching_oat = space_to_skip_patching_map.find(space)->second;
- if (!skip_patching_oat) {
- std::string output_vdex_filename =
- ImageHeader::GetVdexLocationFromImageLocation(output_image_filename);
- std::string output_oat_filename =
- ImageHeader::GetOatLocationFromImageLocation(output_image_filename);
-
- std::unique_ptr<File>
- output_oat_file(CreateOrOpen(output_oat_filename.c_str(), &new_oat_out));
- if (output_oat_file.get() == nullptr) {
- LOG(ERROR) << "Failed to open output oat file at " << output_oat_filename;
- return false;
- }
- success = p.WriteElf(output_oat_file.get());
- success = FinishFile(output_oat_file.get(), success);
- if (success) {
- success = SymlinkFile(input_vdex_filename, output_vdex_filename);
- }
- if (!success) {
- return false;
- }
- }
}
return true;
}
-bool PatchOat::WriteElf(File* out) {
- TimingLogger::ScopedTiming t("Writing Elf File", timings_);
-
- CHECK(oat_file_.get() != nullptr);
- CHECK(out != nullptr);
- size_t expect = oat_file_->Size();
- if (out->WriteFully(reinterpret_cast<char*>(oat_file_->Begin()), expect) &&
- out->SetLength(expect) == 0) {
- return true;
- } else {
- LOG(ERROR) << "Writing to oat file " << out->GetPath() << " failed.";
- return false;
- }
-}
-
bool PatchOat::WriteImage(File* out) {
TimingLogger::ScopedTiming t("Writing image File", timings_);
std::string error_msg;
@@ -466,22 +350,7 @@
}
bool PatchOat::ReplaceOatFileWithSymlink(const std::string& input_oat_filename,
- const std::string& output_oat_filename,
- bool output_oat_opened_from_fd,
- bool new_oat_out) {
- // Need a file when we are PIC, since we symlink over it. Refusing to symlink into FD.
- if (output_oat_opened_from_fd) {
- // TODO: installd uses --output-oat-fd. Should we change class linking logic for PIC?
- LOG(ERROR) << "No output oat filename specified, needs filename for when we are PIC";
- return false;
- }
-
- // Image was PIC. Create symlink where the oat is supposed to go.
- if (!new_oat_out) {
- LOG(ERROR) << "Oat file " << output_oat_filename << " already exists, refusing to overwrite";
- return false;
- }
-
+ const std::string& output_oat_filename) {
// Delete the original file, since we won't need it.
unlink(output_oat_filename.c_str());
@@ -643,8 +512,8 @@
if (orig_strings != nullptr) {
orig_dex_cache->FixupStrings(RelocatedCopyOf(orig_strings), RelocatedPointerVisitor(this));
}
- GcRoot<mirror::Class>* orig_types = orig_dex_cache->GetResolvedTypes();
- GcRoot<mirror::Class>* relocated_types = RelocatedAddressOfPointer(orig_types);
+ mirror::TypeDexCacheType* orig_types = orig_dex_cache->GetResolvedTypes();
+ mirror::TypeDexCacheType* relocated_types = RelocatedAddressOfPointer(orig_types);
copy_dex_cache->SetField64<false>(
mirror::DexCache::ResolvedTypesOffset(),
static_cast<int64_t>(reinterpret_cast<uintptr_t>(relocated_types)));
@@ -807,133 +676,6 @@
object->GetDataPtrSize(pointer_size)), pointer_size);
}
-bool PatchOat::Patch(File* input_oat, off_t delta, File* output_oat, TimingLogger* timings,
- bool output_oat_opened_from_fd, bool new_oat_out) {
- CHECK(input_oat != nullptr);
- CHECK(output_oat != nullptr);
- CHECK_GE(input_oat->Fd(), 0);
- CHECK_GE(output_oat->Fd(), 0);
- TimingLogger::ScopedTiming t("Setup Oat File Patching", timings);
-
- std::string error_msg;
- std::unique_ptr<ElfFile> elf(ElfFile::Open(input_oat,
- PROT_READ | PROT_WRITE, MAP_PRIVATE, &error_msg));
- if (elf.get() == nullptr) {
- LOG(ERROR) << "unable to open oat file " << input_oat->GetPath() << " : " << error_msg;
- return false;
- }
-
- MaybePic is_oat_pic = IsOatPic(elf.get());
- if (is_oat_pic >= ERROR_FIRST) {
- // Error logged by IsOatPic
- return false;
- } else if (is_oat_pic == PIC) {
- // Do not need to do ELF-file patching. Create a symlink and skip the rest.
- // Any errors will be logged by the function call.
- return ReplaceOatFileWithSymlink(input_oat->GetPath(),
- output_oat->GetPath(),
- output_oat_opened_from_fd,
- new_oat_out);
- } else {
- CHECK(is_oat_pic == NOT_PIC);
- }
-
- PatchOat p(elf.release(), delta, timings);
- t.NewTiming("Patch Oat file");
- if (!p.PatchElf()) {
- return false;
- }
-
- t.NewTiming("Writing oat file");
- if (!p.WriteElf(output_oat)) {
- return false;
- }
- return true;
-}
-
-template <typename ElfFileImpl>
-bool PatchOat::PatchOatHeader(ElfFileImpl* oat_file) {
- auto rodata_sec = oat_file->FindSectionByName(".rodata");
- if (rodata_sec == nullptr) {
- return false;
- }
- OatHeader* oat_header = reinterpret_cast<OatHeader*>(oat_file->Begin() + rodata_sec->sh_offset);
- if (!oat_header->IsValid()) {
- LOG(ERROR) << "Elf file " << oat_file->GetFilePath() << " has an invalid oat header";
- return false;
- }
- oat_header->RelocateOat(delta_);
- return true;
-}
-
-bool PatchOat::PatchElf() {
- if (oat_file_->Is64Bit()) {
- return PatchElf<ElfFileImpl64>(oat_file_->GetImpl64());
- } else {
- return PatchElf<ElfFileImpl32>(oat_file_->GetImpl32());
- }
-}
-
-template <typename ElfFileImpl>
-bool PatchOat::PatchElf(ElfFileImpl* oat_file) {
- TimingLogger::ScopedTiming t("Fixup Elf Text Section", timings_);
-
- // Fix up absolute references to locations within the boot image.
- if (!oat_file->ApplyOatPatchesTo(".text", delta_)) {
- return false;
- }
-
- // Update the OatHeader fields referencing the boot image.
- if (!PatchOatHeader<ElfFileImpl>(oat_file)) {
- return false;
- }
-
- bool need_boot_oat_fixup = true;
- for (unsigned int i = 0; i < oat_file->GetProgramHeaderNum(); ++i) {
- auto hdr = oat_file->GetProgramHeader(i);
- if (hdr->p_type == PT_LOAD && hdr->p_vaddr == 0u) {
- need_boot_oat_fixup = false;
- break;
- }
- }
- if (!need_boot_oat_fixup) {
- // This is an app oat file that can be loaded at an arbitrary address in memory.
- // Boot image references were patched above and there's nothing else to do.
- return true;
- }
-
- // This is a boot oat file that's loaded at a particular address and we need
- // to patch all absolute addresses, starting with ELF program headers.
-
- t.NewTiming("Fixup Elf Headers");
- // Fixup Phdr's
- oat_file->FixupProgramHeaders(delta_);
-
- t.NewTiming("Fixup Section Headers");
- // Fixup Shdr's
- oat_file->FixupSectionHeaders(delta_);
-
- t.NewTiming("Fixup Dynamics");
- oat_file->FixupDynamic(delta_);
-
- t.NewTiming("Fixup Elf Symbols");
- // Fixup dynsym
- if (!oat_file->FixupSymbols(delta_, true)) {
- return false;
- }
- // Fixup symtab
- if (!oat_file->FixupSymbols(delta_, false)) {
- return false;
- }
-
- t.NewTiming("Fixup Debug Sections");
- if (!oat_file->FixupDebugSections(delta_)) {
- return false;
- }
-
- return true;
-}
-
static int orig_argc;
static char** orig_argv;
@@ -968,32 +710,10 @@
UsageError("Usage: patchoat [options]...");
UsageError("");
UsageError(" --instruction-set=<isa>: Specifies the instruction set the patched code is");
- UsageError(" compiled for. Required if you use --input-oat-location");
- UsageError("");
- UsageError(" --input-oat-file=<file.oat>: Specifies the exact filename of the oat file to be");
- UsageError(" patched.");
- UsageError("");
- UsageError(" --input-oat-fd=<file-descriptor>: Specifies the file-descriptor of the oat file");
- UsageError(" to be patched.");
- UsageError("");
- UsageError(" --input-vdex-fd=<file-descriptor>: Specifies the file-descriptor of the vdex file");
- UsageError(" associated with the oat file.");
- UsageError("");
- UsageError(" --input-oat-location=<file.oat>: Specifies the 'location' to read the patched");
- UsageError(" oat file from. If used one must also supply the --instruction-set");
+ UsageError(" compiled for (required).");
UsageError("");
UsageError(" --input-image-location=<file.art>: Specifies the 'location' of the image file to");
- UsageError(" be patched. If --instruction-set is not given it will use the instruction set");
- UsageError(" extracted from the --input-oat-file.");
- UsageError("");
- UsageError(" --output-oat-file=<file.oat>: Specifies the exact file to write the patched oat");
- UsageError(" file to.");
- UsageError("");
- UsageError(" --output-oat-fd=<file-descriptor>: Specifies the file-descriptor to write the");
- UsageError(" patched oat file to.");
- UsageError("");
- UsageError(" --output-vdex-fd=<file-descriptor>: Specifies the file-descriptor to copy the");
- UsageError(" the vdex file associated with the patch oat file to.");
+ UsageError(" be patched.");
UsageError("");
UsageError(" --output-image-file=<file.art>: Specifies the exact file to write the patched");
UsageError(" image file to.");
@@ -1001,15 +721,6 @@
UsageError(" --base-offset-delta=<delta>: Specify the amount to change the old base-offset by.");
UsageError(" This value may be negative.");
UsageError("");
- UsageError(" --patched-image-location=<file.art>: Relocate the oat file to be the same as the");
- UsageError(" image at the given location. If used one must also specify the");
- UsageError(" --instruction-set flag. It will search for this image in the same way that");
- UsageError(" is done when loading one.");
- UsageError("");
- UsageError(" --lock-output: Obtain a flock on output oat file before starting.");
- UsageError("");
- UsageError(" --no-lock-output: Do not attempt to obtain a flock on output oat file.");
- UsageError("");
UsageError(" --dump-timings: dump out patch timing information");
UsageError("");
UsageError(" --no-dump-timings: do not dump out patch timing information");
@@ -1018,34 +729,6 @@
exit(EXIT_FAILURE);
}
-static bool ReadBaseDelta(const char* name, off_t* delta, std::string* error_msg) {
- CHECK(name != nullptr);
- CHECK(delta != nullptr);
- std::unique_ptr<File> file;
- if (OS::FileExists(name)) {
- file.reset(OS::OpenFileForReading(name));
- if (file.get() == nullptr) {
- *error_msg = "Failed to open file %s for reading";
- return false;
- }
- } else {
- *error_msg = "File %s does not exist";
- return false;
- }
- CHECK(file.get() != nullptr);
- ImageHeader hdr;
- if (sizeof(hdr) != file->Read(reinterpret_cast<char*>(&hdr), sizeof(hdr), 0)) {
- *error_msg = "Failed to read file %s";
- return false;
- }
- if (!hdr.IsValid()) {
- *error_msg = "%s does not contain a valid image header.";
- return false;
- }
- *delta = hdr.GetPatchDelta();
- return true;
-}
-
static int patchoat_image(TimingLogger& timings,
InstructionSet isa,
const std::string& input_image_location,
@@ -1084,293 +767,6 @@
return ret ? EXIT_SUCCESS : EXIT_FAILURE;
}
-static int patchoat_oat(TimingLogger& timings,
- InstructionSet isa,
- const std::string& patched_image_location,
- off_t base_delta,
- bool base_delta_set,
- int input_oat_fd,
- int input_vdex_fd,
- const std::string& input_oat_location,
- std::string input_oat_filename,
- bool have_input_oat,
- int output_oat_fd,
- int output_vdex_fd,
- std::string output_oat_filename,
- bool have_output_oat,
- bool lock_output,
- bool debug) {
- {
- // Only 1 of these may be set.
- uint32_t cnt = 0;
- cnt += (base_delta_set) ? 1 : 0;
- cnt += (!patched_image_location.empty()) ? 1 : 0;
- if (cnt > 1) {
- Usage("Only one of --base-offset-delta or --patched-image-location may be used.");
- } else if (cnt == 0) {
- Usage("Must specify --base-offset-delta or --patched-image-location.");
- }
- }
-
- if (!have_input_oat || !have_output_oat) {
- Usage("Both input and output oat must be supplied to patch an app odex.");
- }
-
- if (!input_oat_location.empty()) {
- if (!LocationToFilename(input_oat_location, isa, &input_oat_filename)) {
- Usage("Unable to find filename for input oat location %s", input_oat_location.c_str());
- }
- if (debug) {
- LOG(INFO) << "Using input-oat-file " << input_oat_filename;
- }
- }
-
- if ((input_oat_fd == -1) != (input_vdex_fd == -1)) {
- Usage("Either both input oat and vdex have to be passed as file descriptors or none of them");
- } else if ((output_oat_fd == -1) != (output_vdex_fd == -1)) {
- Usage("Either both output oat and vdex have to be passed as file descriptors or none of them");
- }
-
- bool match_delta = false;
- if (!patched_image_location.empty()) {
- std::string system_filename;
- bool has_system = false;
- std::string cache_filename;
- bool has_cache = false;
- bool has_android_data_unused = false;
- bool is_global_cache = false;
- if (!gc::space::ImageSpace::FindImageFilename(patched_image_location.c_str(), isa,
- &system_filename, &has_system, &cache_filename,
- &has_android_data_unused, &has_cache,
- &is_global_cache)) {
- Usage("Unable to determine image file for location %s", patched_image_location.c_str());
- }
- std::string patched_image_filename;
- if (has_cache) {
- patched_image_filename = cache_filename;
- } else if (has_system) {
- LOG(WARNING) << "Only image file found was in /system for image location "
- << patched_image_location;
- patched_image_filename = system_filename;
- } else {
- Usage("Unable to determine image file for location %s", patched_image_location.c_str());
- }
- if (debug) {
- LOG(INFO) << "Using patched-image-file " << patched_image_filename;
- }
-
- base_delta_set = true;
- match_delta = true;
- std::string error_msg;
- if (!ReadBaseDelta(patched_image_filename.c_str(), &base_delta, &error_msg)) {
- Usage(error_msg.c_str(), patched_image_filename.c_str());
- }
- }
-
- if (!IsAligned<kPageSize>(base_delta)) {
- Usage("Base offset/delta must be alligned to a pagesize (0x%08x) boundary.", kPageSize);
- }
-
- // We can symlink VDEX only if we have both input and output specified as filenames.
- // Store that piece of information before we possibly create bogus filenames for
- // files passed as file descriptors.
- bool symlink_vdex = !input_oat_filename.empty() && !output_oat_filename.empty();
-
- // Infer names of VDEX files.
- std::string input_vdex_filename;
- std::string output_vdex_filename;
- if (!input_oat_filename.empty()) {
- input_vdex_filename = ReplaceFileExtension(input_oat_filename, "vdex");
- }
- if (!output_oat_filename.empty()) {
- output_vdex_filename = ReplaceFileExtension(output_oat_filename, "vdex");
- }
-
- // Do we need to cleanup output files if we fail?
- bool new_oat_out = false;
- bool new_vdex_out = false;
-
- std::unique_ptr<File> input_oat;
- std::unique_ptr<File> output_oat;
-
- if (input_oat_fd != -1) {
- if (input_oat_filename.empty()) {
- input_oat_filename = "input-oat-file";
- }
- input_oat.reset(new File(input_oat_fd, input_oat_filename, false));
- if (input_oat_fd == output_oat_fd) {
- input_oat.get()->DisableAutoClose();
- }
- if (input_oat == nullptr) {
- // Unlikely, but ensure exhaustive logging in non-0 exit code case
- LOG(ERROR) << "Failed to open input oat file by its FD" << input_oat_fd;
- return EXIT_FAILURE;
- }
- } else {
- CHECK(!input_oat_filename.empty());
- input_oat.reset(OS::OpenFileForReading(input_oat_filename.c_str()));
- if (input_oat == nullptr) {
- int err = errno;
- LOG(ERROR) << "Failed to open input oat file " << input_oat_filename
- << ": " << strerror(err) << "(" << err << ")";
- return EXIT_FAILURE;
- }
- }
-
- std::string error_msg;
- std::unique_ptr<ElfFile> elf(ElfFile::Open(input_oat.get(), PROT_READ, MAP_PRIVATE, &error_msg));
- if (elf.get() == nullptr) {
- LOG(ERROR) << "unable to open oat file " << input_oat->GetPath() << " : " << error_msg;
- return EXIT_FAILURE;
- }
- if (!elf->HasSection(".text.oat_patches")) {
- LOG(ERROR) << "missing oat patch section in input oat file " << input_oat->GetPath();
- return EXIT_FAILURE;
- }
-
- if (output_oat_fd != -1) {
- if (output_oat_filename.empty()) {
- output_oat_filename = "output-oat-file";
- }
- output_oat.reset(new File(output_oat_fd, output_oat_filename, true));
- if (output_oat == nullptr) {
- // Unlikely, but ensure exhaustive logging in non-0 exit code case
- LOG(ERROR) << "Failed to open output oat file by its FD" << output_oat_fd;
- }
- } else {
- CHECK(!output_oat_filename.empty());
- output_oat.reset(CreateOrOpen(output_oat_filename.c_str(), &new_oat_out));
- if (output_oat == nullptr) {
- int err = errno;
- LOG(ERROR) << "Failed to open output oat file " << output_oat_filename
- << ": " << strerror(err) << "(" << err << ")";
- }
- }
-
- // Open VDEX files if we are not symlinking them.
- std::unique_ptr<File> input_vdex;
- std::unique_ptr<File> output_vdex;
- if (symlink_vdex) {
- new_vdex_out = !OS::FileExists(output_vdex_filename.c_str());
- } else {
- if (input_vdex_fd != -1) {
- input_vdex.reset(new File(input_vdex_fd, input_vdex_filename, true));
- if (input_vdex == nullptr) {
- // Unlikely, but ensure exhaustive logging in non-0 exit code case
- LOG(ERROR) << "Failed to open input vdex file by its FD" << input_vdex_fd;
- }
- } else {
- input_vdex.reset(OS::OpenFileForReading(input_vdex_filename.c_str()));
- if (input_vdex == nullptr) {
- PLOG(ERROR) << "Failed to open input vdex file " << input_vdex_filename;
- return EXIT_FAILURE;
- }
- }
- if (output_vdex_fd != -1) {
- output_vdex.reset(new File(output_vdex_fd, output_vdex_filename, true));
- if (output_vdex == nullptr) {
- // Unlikely, but ensure exhaustive logging in non-0 exit code case
- LOG(ERROR) << "Failed to open output vdex file by its FD" << output_vdex_fd;
- }
- } else {
- output_vdex.reset(CreateOrOpen(output_vdex_filename.c_str(), &new_vdex_out));
- if (output_vdex == nullptr) {
- PLOG(ERROR) << "Failed to open output vdex file " << output_vdex_filename;
- return EXIT_FAILURE;
- }
- }
- }
-
- // TODO: get rid of this.
- auto cleanup = [&output_oat_filename, &output_vdex_filename, &new_oat_out, &new_vdex_out]
- (bool success) {
- if (!success) {
- if (new_oat_out) {
- CHECK(!output_oat_filename.empty());
- unlink(output_oat_filename.c_str());
- }
- if (new_vdex_out) {
- CHECK(!output_vdex_filename.empty());
- unlink(output_vdex_filename.c_str());
- }
- }
-
- if (kIsDebugBuild) {
- LOG(INFO) << "Cleaning up.. success? " << success;
- }
- };
-
- if (output_oat.get() == nullptr) {
- cleanup(false);
- return EXIT_FAILURE;
- }
-
- if (match_delta) {
- // Figure out what the current delta is so we can match it to the desired delta.
- off_t current_delta = 0;
- if (!ReadOatPatchDelta(elf.get(), ¤t_delta, &error_msg)) {
- LOG(ERROR) << "Unable to get current delta: " << error_msg;
- cleanup(false);
- return EXIT_FAILURE;
- }
- // Before this line base_delta is the desired final delta. We need it to be the actual amount to
- // change everything by. We subtract the current delta from it to make it this.
- base_delta -= current_delta;
- if (!IsAligned<kPageSize>(base_delta)) {
- LOG(ERROR) << "Given image file was relocated by an illegal delta";
- cleanup(false);
- return false;
- }
- }
-
- if (debug) {
- LOG(INFO) << "moving offset by " << base_delta
- << " (0x" << std::hex << base_delta << ") bytes or "
- << std::dec << (base_delta/kPageSize) << " pages.";
- }
-
- ScopedFlock output_oat_lock;
- if (lock_output) {
- if (!output_oat_lock.Init(output_oat.get(), &error_msg)) {
- LOG(ERROR) << "Unable to lock output oat " << output_oat->GetPath() << ": " << error_msg;
- cleanup(false);
- return EXIT_FAILURE;
- }
- }
-
- TimingLogger::ScopedTiming pt("patch oat", &timings);
- bool ret = PatchOat::Patch(input_oat.get(), base_delta, output_oat.get(), &timings,
- output_oat_fd >= 0, // was it opened from FD?
- new_oat_out);
- ret = FinishFile(output_oat.get(), ret);
-
- if (ret) {
- if (symlink_vdex) {
- ret = SymlinkFile(input_vdex_filename, output_vdex_filename);
- } else {
- ret = unix_file::CopyFile(*input_vdex.get(), output_vdex.get());
- }
- }
-
- if (kIsDebugBuild) {
- LOG(INFO) << "Exiting with return ... " << ret;
- }
- cleanup(ret);
- return ret ? EXIT_SUCCESS : EXIT_FAILURE;
-}
-
-static int ParseFd(const StringPiece& option, const char* cmdline_arg) {
- int fd;
- const char* fd_str = option.substr(strlen(cmdline_arg)).data();
- if (!ParseInt(fd_str, &fd)) {
- Usage("Failed to parse %d argument '%s' as an integer", cmdline_arg, fd_str);
- }
- if (fd < 0) {
- Usage("%s pass a negative value %d", cmdline_arg, fd);
- }
- return fd;
-}
-
static int patchoat(int argc, char **argv) {
InitLogging(argv, Runtime::Aborter);
MemMap::Init();
@@ -1392,23 +788,11 @@
// cmd line args
bool isa_set = false;
InstructionSet isa = kNone;
- std::string input_oat_filename;
- std::string input_oat_location;
- int input_oat_fd = -1;
- int input_vdex_fd = -1;
- bool have_input_oat = false;
std::string input_image_location;
- std::string output_oat_filename;
- int output_oat_fd = -1;
- int output_vdex_fd = -1;
- bool have_output_oat = false;
std::string output_image_filename;
off_t base_delta = 0;
bool base_delta_set = false;
- std::string patched_image_filename;
- std::string patched_image_location;
bool dump_timings = kIsDebugBuild;
- bool lock_output = true;
for (int i = 0; i < argc; ++i) {
const StringPiece option(argv[i]);
@@ -1423,42 +807,8 @@
if (isa == kNone) {
Usage("Unknown or invalid instruction set %s", isa_str);
}
- } else if (option.starts_with("--input-oat-location=")) {
- if (have_input_oat) {
- Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used.");
- }
- have_input_oat = true;
- input_oat_location = option.substr(strlen("--input-oat-location=")).data();
- } else if (option.starts_with("--input-oat-file=")) {
- if (have_input_oat) {
- Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used.");
- }
- have_input_oat = true;
- input_oat_filename = option.substr(strlen("--input-oat-file=")).data();
- } else if (option.starts_with("--input-oat-fd=")) {
- if (have_input_oat) {
- Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used.");
- }
- have_input_oat = true;
- input_oat_fd = ParseFd(option, "--input-oat-fd=");
- } else if (option.starts_with("--input-vdex-fd=")) {
- input_vdex_fd = ParseFd(option, "--input-vdex-fd=");
} else if (option.starts_with("--input-image-location=")) {
input_image_location = option.substr(strlen("--input-image-location=")).data();
- } else if (option.starts_with("--output-oat-file=")) {
- if (have_output_oat) {
- Usage("Only one of --output-oat-file, and --output-oat-fd may be used.");
- }
- have_output_oat = true;
- output_oat_filename = option.substr(strlen("--output-oat-file=")).data();
- } else if (option.starts_with("--output-oat-fd=")) {
- if (have_output_oat) {
- Usage("Only one of --output-oat-file, --output-oat-fd may be used.");
- }
- have_output_oat = true;
- output_oat_fd = ParseFd(option, "--output-oat-fd=");
- } else if (option.starts_with("--output-vdex-fd=")) {
- output_vdex_fd = ParseFd(option, "--output-vdex-fd=");
} else if (option.starts_with("--output-image-file=")) {
output_image_filename = option.substr(strlen("--output-image-file=")).data();
} else if (option.starts_with("--base-offset-delta=")) {
@@ -1467,12 +817,6 @@
if (!ParseInt(base_delta_str, &base_delta)) {
Usage("Failed to parse --base-offset-delta argument '%s' as an off_t", base_delta_str);
}
- } else if (option.starts_with("--patched-image-location=")) {
- patched_image_location = option.substr(strlen("--patched-image-location=")).data();
- } else if (option == "--lock-output") {
- lock_output = true;
- } else if (option == "--no-lock-output") {
- lock_output = false;
} else if (option == "--dump-timings") {
dump_timings = true;
} else if (option == "--no-dump-timings") {
@@ -1487,33 +831,13 @@
Usage("Instruction set must be set.");
}
- int ret;
- if (!input_image_location.empty()) {
- ret = patchoat_image(timings,
- isa,
- input_image_location,
- output_image_filename,
- base_delta,
- base_delta_set,
- debug);
- } else {
- ret = patchoat_oat(timings,
- isa,
- patched_image_location,
- base_delta,
- base_delta_set,
- input_oat_fd,
- input_vdex_fd,
- input_oat_location,
- input_oat_filename,
- have_input_oat,
- output_oat_fd,
- output_vdex_fd,
- output_oat_filename,
- have_output_oat,
- lock_output,
- debug);
- }
+ int ret = patchoat_image(timings,
+ isa,
+ input_image_location,
+ output_image_filename,
+ base_delta,
+ base_delta_set,
+ debug);
timings.EndTiming();
if (dump_timings) {
diff --git a/patchoat/patchoat.h b/patchoat/patchoat.h
index a519631..e15a6bc 100644
--- a/patchoat/patchoat.h
+++ b/patchoat/patchoat.h
@@ -44,17 +44,7 @@
class PatchOat {
public:
- // Patch only the oat file
- static bool Patch(File* oat_in, off_t delta, File* oat_out, TimingLogger* timings,
- bool output_oat_opened_from_fd, // Was this using --oatput-oat-fd ?
- bool new_oat_out); // Output oat was a new file created by us?
-
- // Patch only the image (art file)
- static bool Patch(const std::string& art_location, off_t delta, File* art_out, InstructionSet isa,
- TimingLogger* timings);
-
- // Patch both the image and the oat file
- static bool Patch(const std::string& art_location,
+ static bool Patch(const std::string& image_location,
off_t delta,
const std::string& output_directory,
InstructionSet isa,
@@ -64,18 +54,11 @@
PatchOat(PatchOat&&) = default;
private:
- // Takes ownership only of the ElfFile. All other pointers are only borrowed.
- PatchOat(ElfFile* oat_file, off_t delta, TimingLogger* timings)
- : oat_file_(oat_file), image_(nullptr), bitmap_(nullptr), heap_(nullptr), delta_(delta),
- isa_(kNone), space_map_(nullptr), timings_(timings) {}
- PatchOat(InstructionSet isa, MemMap* image, gc::accounting::ContinuousSpaceBitmap* bitmap,
- MemMap* heap, off_t delta, TimingLogger* timings)
- : image_(image), bitmap_(bitmap), heap_(heap),
- delta_(delta), isa_(isa), space_map_(nullptr), timings_(timings) {}
- PatchOat(InstructionSet isa, ElfFile* oat_file, MemMap* image,
+ // All pointers are only borrowed.
+ PatchOat(InstructionSet isa, MemMap* image,
gc::accounting::ContinuousSpaceBitmap* bitmap, MemMap* heap, off_t delta,
std::map<gc::space::ImageSpace*, std::unique_ptr<MemMap>>* map, TimingLogger* timings)
- : oat_file_(oat_file), image_(image), bitmap_(bitmap), heap_(heap),
+ : image_(image), bitmap_(bitmap), heap_(heap),
delta_(delta), isa_(isa), space_map_(map), timings_(timings) {}
// Was the .art image at image_path made with --compile-pic ?
@@ -94,9 +77,7 @@
// Attempt to replace the file with a symlink
// Returns false if it fails
static bool ReplaceOatFileWithSymlink(const std::string& input_oat_filename,
- const std::string& output_oat_filename,
- bool output_oat_opened_from_fd,
- bool new_oat_out); // Output oat was newly created?
+ const std::string& output_oat_filename);
static void BitmapCallback(mirror::Object* obj, void* arg)
REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -108,13 +89,6 @@
void FixupMethod(ArtMethod* object, ArtMethod* copy)
REQUIRES_SHARED(Locks::mutator_lock_);
- // Patches oat in place, modifying the oat_file given to the constructor.
- bool PatchElf();
- template <typename ElfFileImpl>
- bool PatchElf(ElfFileImpl* oat_file);
- template <typename ElfFileImpl>
- bool PatchOatHeader(ElfFileImpl* oat_file);
-
bool PatchImage(bool primary_image) REQUIRES_SHARED(Locks::mutator_lock_);
void PatchArtFields(const ImageHeader* image_header) REQUIRES_SHARED(Locks::mutator_lock_);
void PatchArtMethods(const ImageHeader* image_header) REQUIRES_SHARED(Locks::mutator_lock_);
@@ -128,7 +102,6 @@
void PatchDexFileArrays(mirror::ObjectArray<mirror::Object>* img_roots)
REQUIRES_SHARED(Locks::mutator_lock_);
- bool WriteElf(File* out);
bool WriteImage(File* out);
template <typename T>
@@ -175,19 +148,6 @@
return reinterpret_cast<T*>(ret);
}
- template <typename T>
- T RelocatedAddressOfIntPointer(T obj) const {
- if (obj == 0) {
- return obj;
- }
- T ret = obj + delta_;
- // Trim off high bits in case negative relocation with 64 bit patchoat.
- if (Is32BitISA()) {
- ret = static_cast<T>(static_cast<uint32_t>(ret));
- }
- return ret;
- }
-
bool Is32BitISA() const {
return InstructionSetPointerSize(isa_) == PointerSize::k32;
}
@@ -213,8 +173,6 @@
mirror::Object* const copy_;
};
- // The elf file we are patching.
- std::unique_ptr<ElfFile> oat_file_;
// A mmap of the image we are patching. This is modified.
const MemMap* const image_;
// The bitmap over the image within the heap we are patching. This is not modified.
diff --git a/profman/profman.cc b/profman/profman.cc
index 8f35a76..a42e4f1 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -593,7 +593,7 @@
}
// Generate the profile data structure.
ProfileCompilationInfo info;
- std::vector<MethodReference> methods; // No methods for now.
+ std::vector<ProfileMethodInfo> methods; // No methods for now.
info.AddMethodsAndClasses(methods, resolved_class_set);
// Write the profile file.
CHECK(info.Save(fd));
diff --git a/runtime/art_field-inl.h b/runtime/art_field-inl.h
index 80af8e7..16b73c6 100644
--- a/runtime/art_field-inl.h
+++ b/runtime/art_field-inl.h
@@ -311,6 +311,8 @@
template <bool kResolve>
inline ObjPtr<mirror::Class> ArtField::GetType() {
+ // TODO: Refactor this function into two functions, ResolveType() and LookupType()
+ // so that we can properly annotate it with no-suspension possible / suspension possible.
const uint32_t field_index = GetDexFieldIndex();
ObjPtr<mirror::Class> declaring_class = GetDeclaringClass();
if (UNLIKELY(declaring_class->IsProxyClass())) {
@@ -320,9 +322,16 @@
const DexFile* const dex_file = dex_cache->GetDexFile();
const DexFile::FieldId& field_id = dex_file->GetFieldId(field_index);
ObjPtr<mirror::Class> type = dex_cache->GetResolvedType(field_id.type_idx_);
- if (kResolve && UNLIKELY(type == nullptr)) {
- type = ResolveGetType(field_id.type_idx_);
- CHECK(type != nullptr || Thread::Current()->IsExceptionPending());
+ if (UNLIKELY(type == nullptr)) {
+ ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+ if (kResolve) {
+ type = class_linker->ResolveType(*dex_file, field_id.type_idx_, declaring_class);
+ CHECK(type != nullptr || Thread::Current()->IsExceptionPending());
+ } else {
+ type = class_linker->LookupResolvedType(
+ *dex_file, field_id.type_idx_, dex_cache, declaring_class->GetClassLoader());
+ DCHECK(!Thread::Current()->IsExceptionPending());
+ }
}
return type;
}
diff --git a/runtime/art_field.cc b/runtime/art_field.cc
index a4a6e5a..7e13104 100644
--- a/runtime/art_field.cc
+++ b/runtime/art_field.cc
@@ -48,10 +48,6 @@
return Runtime::Current()->GetClassLinker()->FindSystemClass(Thread::Current(), descriptor);
}
-ObjPtr<mirror::Class> ArtField::ResolveGetType(dex::TypeIndex type_idx) {
- return Runtime::Current()->GetClassLinker()->ResolveType(type_idx, this);
-}
-
ObjPtr<mirror::String> ArtField::ResolveGetStringName(Thread* self,
const DexFile& dex_file,
dex::StringIndex string_idx,
diff --git a/runtime/art_field.h b/runtime/art_field.h
index 427e103..75dd981 100644
--- a/runtime/art_field.h
+++ b/runtime/art_field.h
@@ -217,8 +217,6 @@
private:
ObjPtr<mirror::Class> ProxyFindSystemClass(const char* descriptor)
REQUIRES_SHARED(Locks::mutator_lock_);
- ObjPtr<mirror::Class> ResolveGetType(dex::TypeIndex type_idx)
- REQUIRES_SHARED(Locks::mutator_lock_);
ObjPtr<mirror::String> ResolveGetStringName(Thread* self,
const DexFile& dex_file,
dex::StringIndex string_idx,
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index 950f1aa..473d9cf 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -175,12 +175,19 @@
}
inline mirror::Class* ArtMethod::GetClassFromTypeIndex(dex::TypeIndex type_idx, bool resolve) {
+ // TODO: Refactor this function into two functions, Resolve...() and Lookup...()
+ // so that we can properly annotate it with no-suspension possible / suspension possible.
ObjPtr<mirror::DexCache> dex_cache = GetDexCache();
ObjPtr<mirror::Class> type = dex_cache->GetResolvedType(type_idx);
- if (UNLIKELY(type == nullptr) && resolve) {
+ if (UNLIKELY(type == nullptr)) {
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
- type = class_linker->ResolveType(type_idx, this);
- CHECK(type != nullptr || Thread::Current()->IsExceptionPending());
+ if (resolve) {
+ type = class_linker->ResolveType(type_idx, this);
+ CHECK(type != nullptr || Thread::Current()->IsExceptionPending());
+ } else {
+ type = class_linker->LookupResolvedType(
+ *dex_cache->GetDexFile(), type_idx, dex_cache, GetClassLoader());
+ }
}
return type.Ptr();
}
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 3d51fdd..99d7a49 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -223,13 +223,10 @@
}
bool IsObsolete() {
- // TODO Should maybe make this IsIntrinsic check not needed
- return !IsIntrinsic() && (GetAccessFlags() & kAccObsoleteMethod) != 0;
+ return (GetAccessFlags() & kAccObsoleteMethod) != 0;
}
void SetIsObsolete() {
- // TODO We should really support redefining intrinsic if possible.
- DCHECK(!IsIntrinsic());
AddAccessFlags(kAccObsoleteMethod);
}
diff --git a/runtime/base/arena_containers.h b/runtime/base/arena_containers.h
index 2c8aa28..62b974e 100644
--- a/runtime/base/arena_containers.h
+++ b/runtime/base/arena_containers.h
@@ -21,6 +21,7 @@
#include <queue>
#include <set>
#include <stack>
+#include <unordered_map>
#include <utility>
#include "arena_allocator.h"
@@ -85,6 +86,16 @@
Pred,
ArenaAllocatorAdapter<std::pair<Key, Value>>>;
+template <typename Key,
+ typename Value,
+ typename Hash = std::hash<Key>,
+ typename Pred = std::equal_to<Value>>
+using ArenaUnorderedMap = std::unordered_map<Key,
+ Value,
+ Hash,
+ Pred,
+ ArenaAllocatorAdapter<std::pair<const Key, Value>>>;
+
// Implementation details below.
template <bool kCount>
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc
index b93b293..24846e5 100644
--- a/runtime/base/mutex.cc
+++ b/runtime/base/mutex.cc
@@ -46,6 +46,7 @@
ReaderWriterMutex* Locks::heap_bitmap_lock_ = nullptr;
Mutex* Locks::instrument_entrypoints_lock_ = nullptr;
Mutex* Locks::intern_table_lock_ = nullptr;
+Mutex* Locks::jdwp_event_list_lock_ = nullptr;
Mutex* Locks::jni_function_table_lock_ = nullptr;
Mutex* Locks::jni_libraries_lock_ = nullptr;
Mutex* Locks::logging_lock_ = nullptr;
@@ -998,6 +999,7 @@
DCHECK(verifier_deps_lock_ != nullptr);
DCHECK(host_dlopen_handles_lock_ != nullptr);
DCHECK(intern_table_lock_ != nullptr);
+ DCHECK(jdwp_event_list_lock_ != nullptr);
DCHECK(jni_function_table_lock_ != nullptr);
DCHECK(jni_libraries_lock_ != nullptr);
DCHECK(logging_lock_ != nullptr);
@@ -1040,6 +1042,10 @@
DCHECK(runtime_shutdown_lock_ == nullptr);
runtime_shutdown_lock_ = new Mutex("runtime shutdown lock", current_lock_level);
+ UPDATE_CURRENT_LOCK_LEVEL(kJdwpEventListLock);
+ DCHECK(jdwp_event_list_lock_ == nullptr);
+ jdwp_event_list_lock_ = new Mutex("JDWP event list lock", current_lock_level);
+
UPDATE_CURRENT_LOCK_LEVEL(kProfilerLock);
DCHECK(profiler_lock_ == nullptr);
profiler_lock_ = new Mutex("profiler lock", current_lock_level);
@@ -1167,6 +1173,8 @@
expected_mutexes_on_weak_ref_access_.push_back(dex_lock_);
classlinker_classes_lock_->SetShouldRespondToEmptyCheckpointRequest(true);
expected_mutexes_on_weak_ref_access_.push_back(classlinker_classes_lock_);
+ jdwp_event_list_lock_->SetShouldRespondToEmptyCheckpointRequest(true);
+ expected_mutexes_on_weak_ref_access_.push_back(jdwp_event_list_lock_);
jni_libraries_lock_->SetShouldRespondToEmptyCheckpointRequest(true);
expected_mutexes_on_weak_ref_access_.push_back(jni_libraries_lock_);
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index 9b6938f..c59664b 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -630,8 +630,12 @@
// Guards shutdown of the runtime.
static Mutex* runtime_shutdown_lock_ ACQUIRED_AFTER(heap_bitmap_lock_);
+ static Mutex* jdwp_event_list_lock_
+ ACQUIRED_AFTER(runtime_shutdown_lock_)
+ ACQUIRED_BEFORE(breakpoint_lock_);
+
// Guards background profiler global state.
- static Mutex* profiler_lock_ ACQUIRED_AFTER(runtime_shutdown_lock_);
+ static Mutex* profiler_lock_ ACQUIRED_AFTER(jdwp_event_list_lock_);
// Guards trace (ie traceview) requests.
static Mutex* trace_lock_ ACQUIRED_AFTER(profiler_lock_);
diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h
index 3438810..bd510ca 100644
--- a/runtime/class_linker-inl.h
+++ b/runtime/class_linker-inl.h
@@ -78,6 +78,18 @@
return string.Ptr();
}
+inline ObjPtr<mirror::Class> ClassLinker::LookupResolvedType(
+ dex::TypeIndex type_idx,
+ ObjPtr<mirror::DexCache> dex_cache,
+ ObjPtr<mirror::ClassLoader> class_loader) {
+ ObjPtr<mirror::Class> type = dex_cache->GetResolvedType(type_idx);
+ if (type == nullptr) {
+ type = Runtime::Current()->GetClassLinker()->LookupResolvedType(
+ *dex_cache->GetDexFile(), type_idx, dex_cache, class_loader);
+ }
+ return type;
+}
+
inline mirror::Class* ClassLinker::ResolveType(dex::TypeIndex type_idx, ArtMethod* referrer) {
Thread::PoisonObjectPointersIfDebug();
if (kIsDebugBuild) {
@@ -91,25 +103,6 @@
Handle<mirror::ClassLoader> class_loader(hs.NewHandle(declaring_class->GetClassLoader()));
const DexFile& dex_file = *dex_cache->GetDexFile();
resolved_type = ResolveType(dex_file, type_idx, dex_cache, class_loader);
- // Note: We cannot check here to see whether we added the type to the cache. The type
- // might be an erroneous class, which results in it being hidden from us.
- }
- return resolved_type.Ptr();
-}
-
-inline mirror::Class* ClassLinker::ResolveType(dex::TypeIndex type_idx, ArtField* referrer) {
- Thread::PoisonObjectPointersIfDebug();
- ObjPtr<mirror::Class> declaring_class = referrer->GetDeclaringClass();
- ObjPtr<mirror::DexCache> dex_cache_ptr = declaring_class->GetDexCache();
- ObjPtr<mirror::Class> resolved_type = dex_cache_ptr->GetResolvedType(type_idx);
- if (UNLIKELY(resolved_type == nullptr)) {
- StackHandleScope<2> hs(Thread::Current());
- Handle<mirror::DexCache> dex_cache(hs.NewHandle(dex_cache_ptr));
- Handle<mirror::ClassLoader> class_loader(hs.NewHandle(declaring_class->GetClassLoader()));
- const DexFile& dex_file = *dex_cache->GetDexFile();
- resolved_type = ResolveType(dex_file, type_idx, dex_cache, class_loader);
- // Note: We cannot check here to see whether we added the type to the cache. The type
- // might be an erroneous class, which results in it being hidden from us.
}
return resolved_type.Ptr();
}
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index d02cf17..9b0ffaf 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -58,6 +58,7 @@
#include "gc/heap.h"
#include "gc/scoped_gc_critical_section.h"
#include "gc/space/image_space.h"
+#include "gc/space/space-inl.h"
#include "handle_scope-inl.h"
#include "image-inl.h"
#include "imt_conflict_table.h"
@@ -1156,6 +1157,35 @@
ClassTable* const table_;
};
+class VerifyDirectInterfacesInTableClassVisitor {
+ public:
+ explicit VerifyDirectInterfacesInTableClassVisitor(ObjPtr<mirror::ClassLoader> class_loader)
+ : class_loader_(class_loader), self_(Thread::Current()) { }
+
+ bool operator()(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (!klass->IsPrimitive() && klass->GetClassLoader() == class_loader_) {
+ classes_.push_back(klass);
+ }
+ return true;
+ }
+
+ void Check() const REQUIRES_SHARED(Locks::mutator_lock_) {
+ for (ObjPtr<mirror::Class> klass : classes_) {
+ for (uint32_t i = 0, num = klass->NumDirectInterfaces(); i != num; ++i) {
+ CHECK(klass->GetDirectInterface(self_, klass, i) != nullptr)
+ << klass->PrettyDescriptor() << " iface #" << i
+ << klass->GetDexFile().StringByTypeIdx(klass->GetDirectInterfaceTypeIdx(i))
+ << " Bug: 34839984";
+ }
+ }
+ }
+
+ private:
+ ObjPtr<mirror::ClassLoader> class_loader_;
+ Thread* self_;
+ std::vector<ObjPtr<mirror::Class>> classes_;
+};
+
class VerifyDeclaringClassVisitor : public ArtMethodVisitor {
public:
VerifyDeclaringClassVisitor() REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_)
@@ -1187,6 +1217,23 @@
}
}
+template <typename T>
+static void CopyDexCachePairs(const std::atomic<mirror::DexCachePair<T>>* src,
+ size_t count,
+ std::atomic<mirror::DexCachePair<T>>* dst) {
+ DCHECK_NE(count, 0u);
+ DCHECK(!src[0].load(std::memory_order_relaxed).object.IsNull() ||
+ src[0].load(std::memory_order_relaxed).index != 0u);
+ for (size_t i = 0; i < count; ++i) {
+ DCHECK_EQ(dst[i].load(std::memory_order_relaxed).index, 0u);
+ DCHECK(dst[i].load(std::memory_order_relaxed).object.IsNull());
+ mirror::DexCachePair<T> source = src[i].load(std::memory_order_relaxed);
+ if (source.index != 0u || !source.object.IsNull()) {
+ dst[i].store(source, std::memory_order_relaxed);
+ }
+ }
+}
+
bool ClassLinker::UpdateAppImageClassLoadersAndDexCaches(
gc::space::ImageSpace* space,
Handle<mirror::ClassLoader> class_loader,
@@ -1240,7 +1287,10 @@
if (dex_file->NumStringIds() < num_strings) {
num_strings = dex_file->NumStringIds();
}
- const size_t num_types = dex_file->NumTypeIds();
+ size_t num_types = mirror::DexCache::kDexCacheTypeCacheSize;
+ if (dex_file->NumTypeIds() < num_types) {
+ num_types = dex_file->NumTypeIds();
+ }
const size_t num_methods = dex_file->NumMethodIds();
const size_t num_fields = dex_file->NumFieldIds();
size_t num_method_types = mirror::DexCache::kDexCacheMethodTypeCacheSize;
@@ -1260,28 +1310,14 @@
mirror::StringDexCacheType* const image_resolved_strings = dex_cache->GetStrings();
mirror::StringDexCacheType* const strings =
reinterpret_cast<mirror::StringDexCacheType*>(raw_arrays + layout.StringsOffset());
- for (size_t j = 0; j < num_strings; ++j) {
- DCHECK_EQ(strings[j].load(std::memory_order_relaxed).index, 0u);
- DCHECK(strings[j].load(std::memory_order_relaxed).object.IsNull());
- strings[j].store(image_resolved_strings[j].load(std::memory_order_relaxed),
- std::memory_order_relaxed);
- }
- mirror::StringDexCachePair::Initialize(strings);
+ CopyDexCachePairs(image_resolved_strings, num_strings, strings);
dex_cache->SetStrings(strings);
}
if (num_types != 0u) {
- GcRoot<mirror::Class>* const image_resolved_types = dex_cache->GetResolvedTypes();
- GcRoot<mirror::Class>* const types =
- reinterpret_cast<GcRoot<mirror::Class>*>(raw_arrays + layout.TypesOffset());
- for (size_t j = 0; kIsDebugBuild && j < num_types; ++j) {
- DCHECK(types[j].IsNull());
- }
- CopyNonNull(image_resolved_types,
- num_types,
- types,
- [](const GcRoot<mirror::Class>& elem) {
- return elem.IsNull();
- });
+ mirror::TypeDexCacheType* const image_resolved_types = dex_cache->GetResolvedTypes();
+ mirror::TypeDexCacheType* const types =
+ reinterpret_cast<mirror::TypeDexCacheType*>(raw_arrays + layout.TypesOffset());
+ CopyDexCachePairs(image_resolved_types, num_types, types);
dex_cache->SetResolvedTypes(types);
}
if (num_methods != 0u) {
@@ -1322,15 +1358,7 @@
mirror::MethodTypeDexCacheType* const method_types =
reinterpret_cast<mirror::MethodTypeDexCacheType*>(
raw_arrays + layout.MethodTypesOffset());
- for (size_t j = 0; j < num_method_types; ++j) {
- DCHECK_EQ(method_types[j].load(std::memory_order_relaxed).index, 0u);
- DCHECK(method_types[j].load(std::memory_order_relaxed).object.IsNull());
- method_types[j].store(
- image_resolved_method_types[j].load(std::memory_order_relaxed),
- std::memory_order_relaxed);
- }
-
- mirror::MethodTypeDexCachePair::Initialize(method_types);
+ CopyDexCachePairs(image_resolved_method_types, num_method_types, method_types);
dex_cache->SetResolvedMethodTypes(method_types);
}
if (num_call_sites != 0u) {
@@ -1360,11 +1388,11 @@
}
if (kIsDebugBuild) {
CHECK(new_class_set != nullptr);
- GcRoot<mirror::Class>* const types = dex_cache->GetResolvedTypes();
+ mirror::TypeDexCacheType* const types = dex_cache->GetResolvedTypes();
const size_t num_types = dex_cache->NumResolvedTypes();
- for (int32_t j = 0; j < static_cast<int32_t>(num_types); j++) {
+ for (size_t j = 0; j != num_types; ++j) {
// The image space is not yet added to the heap, avoid read barriers.
- ObjPtr<mirror::Class> klass = types[j].Read();
+ ObjPtr<mirror::Class> klass = types[j].load(std::memory_order_relaxed).object.Read();
if (space->HasAddress(klass.Ptr())) {
DCHECK(!klass->IsErroneous()) << klass->GetStatus();
auto it = new_class_set->Find(ClassTable::TableSlot(klass));
@@ -1723,9 +1751,9 @@
// The current dex file field is bogus, overwrite it so that we can get the dex file in the
// loop below.
dex_cache->SetDexFile(dex_file.get());
- GcRoot<mirror::Class>* const types = dex_cache->GetResolvedTypes();
+ mirror::TypeDexCacheType* const types = dex_cache->GetResolvedTypes();
for (int32_t j = 0, num_types = dex_cache->NumResolvedTypes(); j < num_types; j++) {
- ObjPtr<mirror::Class> klass = types[j].Read();
+ ObjPtr<mirror::Class> klass = types[j].load(std::memory_order_relaxed).object.Read();
if (klass != nullptr) {
DCHECK(!klass->IsErroneous()) << klass->GetStatus();
}
@@ -1893,6 +1921,12 @@
VerifyClassInTableArtMethodVisitor visitor2(class_table);
header.VisitPackedArtMethods(&visitor2, space->Begin(), kRuntimePointerSize);
}
+ if (app_image) {
+ // TODO: Restrict this check to debug builds. Bug: 34839984
+ VerifyDirectInterfacesInTableClassVisitor visitor(class_loader.Get());
+ class_table->Visit(visitor);
+ visitor.Check();
+ }
VLOG(class_linker) << "Adding image space took " << PrettyDuration(NanoTime() - start_time);
return true;
}
@@ -1908,6 +1942,13 @@
const bool tracing_enabled = Trace::IsTracingEnabled();
Thread* const self = Thread::Current();
WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
+ if (kUseReadBarrier) {
+ // We do not track new roots for CC.
+ DCHECK_EQ(0, flags & (kVisitRootFlagNewRoots |
+ kVisitRootFlagClearRootLog |
+ kVisitRootFlagStartLoggingNewRoots |
+ kVisitRootFlagStopLoggingNewRoots));
+ }
if ((flags & kVisitRootFlagAllRoots) != 0) {
// Argument for how root visiting deals with ArtField and ArtMethod roots.
// There is 3 GC cases to handle:
@@ -1937,7 +1978,7 @@
root.VisitRoot(visitor, RootInfo(kRootVMInternal));
}
}
- } else if ((flags & kVisitRootFlagNewRoots) != 0) {
+ } else if (!kUseReadBarrier && (flags & kVisitRootFlagNewRoots) != 0) {
for (auto& root : new_class_roots_) {
ObjPtr<mirror::Class> old_ref = root.Read<kWithoutReadBarrier>();
root.VisitRoot(visitor, RootInfo(kRootStickyClass));
@@ -1958,13 +1999,13 @@
}
}
}
- if ((flags & kVisitRootFlagClearRootLog) != 0) {
+ if (!kUseReadBarrier && (flags & kVisitRootFlagClearRootLog) != 0) {
new_class_roots_.clear();
new_bss_roots_boot_oat_files_.clear();
}
- if ((flags & kVisitRootFlagStartLoggingNewRoots) != 0) {
+ if (!kUseReadBarrier && (flags & kVisitRootFlagStartLoggingNewRoots) != 0) {
log_new_roots_ = true;
- } else if ((flags & kVisitRootFlagStopLoggingNewRoots) != 0) {
+ } else if (!kUseReadBarrier && (flags & kVisitRootFlagStopLoggingNewRoots) != 0) {
log_new_roots_ = false;
}
// We deliberately ignore the class roots in the image since we
@@ -3430,6 +3471,11 @@
return nullptr;
}
table->InsertStrongRoot(h_dex_cache.Get());
+ if (h_class_loader.Get() != nullptr) {
+ // Since we added a strong root to the class table, do the write barrier as required for
+ // remembered sets and generational GCs.
+ Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(h_class_loader.Get());
+ }
return h_dex_cache.Get();
}
@@ -4501,6 +4547,108 @@
return CanWeInitializeClass(super_class, can_init_statics, can_init_parents);
}
+std::string DescribeSpace(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
+ std::ostringstream oss;
+ gc::Heap* heap = Runtime::Current()->GetHeap();
+ gc::space::ContinuousSpace* cs = heap->FindContinuousSpaceFromAddress(klass.Ptr());
+ if (cs != nullptr) {
+ if (cs->IsImageSpace()) {
+ oss << "image/" << cs->GetName() << "/" << cs->AsImageSpace()->GetImageFilename();
+ } else {
+ oss << "continuous/" << cs->GetName();
+ }
+ } else {
+ gc::space::DiscontinuousSpace* ds =
+ heap->FindDiscontinuousSpaceFromObject(klass, /* fail_ok */ true);
+ if (ds != nullptr) {
+ oss << "discontinuous/" << ds->GetName();
+ } else {
+ oss << "invalid";
+ }
+ }
+ return oss.str();
+}
+
+std::string DescribeLoaders(ObjPtr<mirror::Class> klass, const char* iface_descriptor)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ std::ostringstream oss;
+ uint32_t hash = ComputeModifiedUtf8Hash(iface_descriptor);
+ ScopedObjectAccessUnchecked soa(Thread::Current());
+ ObjPtr<mirror::Class> path_class_loader =
+ soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader);
+ ObjPtr<mirror::Class> dex_class_loader =
+ soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DexClassLoader);
+
+ // Print the class loader chain.
+ bool found_iface;
+ const char* loader_separator = "";
+ for (ObjPtr<mirror::ClassLoader> loader = klass->GetClassLoader();
+ loader != nullptr;
+ loader = loader->GetParent()) {
+ oss << loader_separator << loader->GetClass()->PrettyDescriptor();
+ loader_separator = ";";
+ // If we didn't find the interface yet, try to find it in the current class loader.
+ if (!found_iface) {
+ ClassTable* table = Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(loader);
+ ObjPtr<mirror::Class> iface =
+ (table != nullptr) ? table->Lookup(iface_descriptor, hash) : nullptr;
+ if (iface != nullptr) {
+ found_iface = true;
+ oss << "[hit:" << DescribeSpace(iface) << "]";
+ }
+ }
+
+ // For PathClassLoader or DexClassLoader also dump the dex file locations.
+ if (loader->GetClass() == path_class_loader || loader->GetClass() == dex_class_loader) {
+ ArtField* const cookie_field =
+ jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie);
+ ArtField* const dex_file_field =
+ jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
+ ObjPtr<mirror::Object> dex_path_list =
+ jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList)->
+ GetObject(loader);
+ if (dex_path_list != nullptr && dex_file_field != nullptr && cookie_field != nullptr) {
+ ObjPtr<mirror::Object> dex_elements_obj =
+ jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements)->
+ GetObject(dex_path_list);
+ if (dex_elements_obj != nullptr) {
+ ObjPtr<mirror::ObjectArray<mirror::Object>> dex_elements =
+ dex_elements_obj->AsObjectArray<mirror::Object>();
+ oss << "(";
+ const char* path_separator = "";
+ for (int32_t i = 0; i != dex_elements->GetLength(); ++i) {
+ ObjPtr<mirror::Object> element = dex_elements->GetWithoutChecks(i);
+ ObjPtr<mirror::Object> dex_file =
+ (element != nullptr) ? dex_file_field->GetObject(element) : nullptr;
+ ObjPtr<mirror::LongArray> long_array =
+ (dex_file != nullptr) ? cookie_field->GetObject(dex_file)->AsLongArray() : nullptr;
+ if (long_array != nullptr) {
+ int32_t long_array_size = long_array->GetLength();
+ // First element is the oat file.
+ for (int32_t j = kDexFileIndexStart; j < long_array_size; ++j) {
+ const DexFile* cp_dex_file = reinterpret_cast<const DexFile*>(
+ static_cast<uintptr_t>(long_array->GetWithoutChecks(j)));
+ oss << path_separator << cp_dex_file->GetLocation();
+ path_separator = ":";
+ }
+ }
+ }
+ oss << ")";
+ }
+ }
+ }
+ }
+
+ // Do a paranoid check that the `klass` itself is in the class table.
+ ClassTable* table =
+ Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(klass->GetClassLoader());
+ ObjPtr<mirror::Class> k = (table != nullptr) ? table->LookupByDescriptor(klass) : nullptr;
+ if (k != klass) {
+ oss << "{FAIL:" << k.Ptr() << "!=" << klass.Ptr() << "}";
+ }
+ return oss.str();
+}
+
bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass,
bool can_init_statics, bool can_init_parents) {
// see JLS 3rd edition, 12.4.2 "Detailed Initialization Procedure" for the locking protocol
@@ -4648,7 +4796,15 @@
MutableHandle<mirror::Class> handle_scope_iface(hs_iface.NewHandle<mirror::Class>(nullptr));
for (size_t i = 0; i < num_direct_interfaces; i++) {
handle_scope_iface.Assign(mirror::Class::GetDirectInterface(self, klass.Get(), i));
- CHECK(handle_scope_iface != nullptr);
+ if (UNLIKELY(handle_scope_iface == nullptr)) {
+ const char* iface_descriptor =
+ klass->GetDexFile().StringByTypeIdx(klass->GetDirectInterfaceTypeIdx(i));
+ LOG(FATAL) << "Check failed: handle_scope_iface != nullptr "
+ << "Debug data for bug 34839984: "
+ << klass->PrettyDescriptor() << " iface #" << i << " " << iface_descriptor
+ << " space: " << DescribeSpace(klass.Get())
+ << " loaders: " << DescribeLoaders(klass.Get(), iface_descriptor);
+ }
CHECK(handle_scope_iface->IsInterface());
if (handle_scope_iface->HasBeenRecursivelyInitialized()) {
// We have already done this for this interface. Skip it.
@@ -7759,7 +7915,9 @@
uint32_t utf16_length;
const char* utf8_data = dex_file.StringDataAndUtf16LengthByIdx(string_idx, &utf16_length);
ObjPtr<mirror::String> string = intern_table_->InternStrong(utf16_length, utf8_data);
- dex_cache->SetResolvedString(string_idx, string);
+ if (string != nullptr) {
+ dex_cache->SetResolvedString(string_idx, string);
+ }
return string.Ptr();
}
@@ -7800,11 +7958,16 @@
// Find the class in the loaded classes table.
type = LookupClass(self, descriptor, hash, class_loader.Ptr());
}
+ if (type != nullptr) {
+ if (type->IsResolved()) {
+ dex_cache->SetResolvedType(type_idx, type);
+ } else {
+ type = nullptr;
+ }
+ }
}
- if (type != nullptr && type->IsResolved()) {
- return type.Ptr();
- }
- return nullptr;
+ DCHECK(type == nullptr || type->IsResolved());
+ return type;
}
mirror::Class* ClassLinker::ResolveType(const DexFile& dex_file,
@@ -7824,6 +7987,12 @@
Thread::PoisonObjectPointersIfDebug();
ObjPtr<mirror::Class> resolved = dex_cache->GetResolvedType(type_idx);
if (resolved == nullptr) {
+ // TODO: Avoid this lookup as it duplicates work done in FindClass(). It is here
+ // as a workaround for FastNative JNI to avoid AssertNoPendingException() when
+ // trying to resolve annotations while an exception may be pending. Bug: 34659969
+ resolved = LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get());
+ }
+ if (resolved == nullptr) {
Thread* self = Thread::Current();
const char* descriptor = dex_file.StringByTypeIdx(type_idx);
resolved = FindClass(self, descriptor, class_loader);
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index e27a53d..33eed3c 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -266,10 +266,6 @@
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
- mirror::Class* ResolveType(dex::TypeIndex type_idx, ArtField* referrer)
- REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
-
// Look up a resolved type with the given ID from the DexFile. The ClassLoader is used to search
// for the type, since it may be referenced from but not contained within the given DexFile.
ObjPtr<mirror::Class> LookupResolvedType(const DexFile& dex_file,
@@ -277,6 +273,10 @@
ObjPtr<mirror::DexCache> dex_cache,
ObjPtr<mirror::ClassLoader> class_loader)
REQUIRES_SHARED(Locks::mutator_lock_);
+ static ObjPtr<mirror::Class> LookupResolvedType(dex::TypeIndex type_idx,
+ ObjPtr<mirror::DexCache> dex_cache,
+ ObjPtr<mirror::ClassLoader> class_loader)
+ REQUIRES_SHARED(Locks::mutator_lock_);
// Resolve a type with the given ID from the DexFile, storing the
// result in DexCache. The ClassLoader is used to search for the
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 07f3744..21cdede 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -935,7 +935,7 @@
class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache, class_loader.Get()),
klass);
// Zero out the resolved type and make sure LookupResolvedType still finds it.
- dex_cache->SetResolvedType(type_idx, nullptr);
+ dex_cache->ClearResolvedType(type_idx);
EXPECT_TRUE(dex_cache->GetResolvedType(type_idx) == nullptr);
EXPECT_OBJ_PTR_EQ(
class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache, class_loader.Get()),
@@ -970,7 +970,7 @@
class_linker_->LookupResolvedType(dex_file, array_idx, dex_cache.Get(), class_loader.Get()),
array_klass);
// Zero out the resolved type and make sure LookupResolvedType() still finds it.
- dex_cache->SetResolvedType(array_idx, nullptr);
+ dex_cache->ClearResolvedType(array_idx);
EXPECT_TRUE(dex_cache->GetResolvedType(array_idx) == nullptr);
EXPECT_OBJ_PTR_EQ(
class_linker_->LookupResolvedType(dex_file, array_idx, dex_cache.Get(), class_loader.Get()),
@@ -993,7 +993,7 @@
class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
klass.Get());
// Zero out the resolved type and make sure LookupResolvedType still finds it.
- dex_cache->SetResolvedType(type_idx, nullptr);
+ dex_cache->ClearResolvedType(type_idx);
EXPECT_TRUE(dex_cache->GetResolvedType(type_idx) == nullptr);
EXPECT_OBJ_PTR_EQ(
class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
@@ -1011,7 +1011,7 @@
class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
klass.Get());
// Zero out the resolved type and make sure LookupResolvedType() still finds it.
- dex_cache->SetResolvedType(type_idx, nullptr);
+ dex_cache->ClearResolvedType(type_idx);
EXPECT_TRUE(dex_cache->GetResolvedType(type_idx) == nullptr);
EXPECT_OBJ_PTR_EQ(
class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc
index 69c6151..5167869 100644
--- a/runtime/dexopt_test.cc
+++ b/runtime/dexopt_test.cc
@@ -70,6 +70,11 @@
// rather than use this flag.
args.push_back("-Xnorelocate");
+ ScratchFile profile_file;
+ if (CompilerFilter::DependsOnProfile(filter)) {
+ args.push_back("--profile-file=" + profile_file.GetFilename());
+ }
+
if (pic) {
args.push_back("--compile-pic");
}
diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h
index 28aca6c..3bc49b8 100644
--- a/runtime/entrypoints/entrypoint_utils-inl.h
+++ b/runtime/entrypoints/entrypoint_utils-inl.h
@@ -709,10 +709,10 @@
return resolved_method;
} else if (type == kSuper) {
// TODO This lookup is rather slow.
- dex::TypeIndex method_type_idx =
- referrer->GetDexFile()->GetMethodId(method_idx).class_idx_;
- mirror::Class* method_reference_class =
- referrer->GetDexCache()->GetResolvedType(method_type_idx);
+ ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache();
+ dex::TypeIndex method_type_idx = dex_cache->GetDexFile()->GetMethodId(method_idx).class_idx_;
+ ObjPtr<mirror::Class> method_reference_class = ClassLinker::LookupResolvedType(
+ method_type_idx, dex_cache, referrer->GetClassLoader());
if (method_reference_class == nullptr) {
// Need to do full type resolution...
return nullptr;
diff --git a/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc b/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
index 699cf91..355d7b3 100644
--- a/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
@@ -32,17 +32,22 @@
namespace art {
static inline void BssWriteBarrier(ArtMethod* outer_method) REQUIRES_SHARED(Locks::mutator_lock_) {
- // For AOT code, we need a write barrier for the class loader that holds
- // the GC roots in the .bss.
+ // For AOT code, we need a write barrier for the class loader that holds the
+ // GC roots in the .bss.
const DexFile* dex_file = outer_method->GetDexFile();
if (dex_file != nullptr &&
dex_file->GetOatDexFile() != nullptr &&
!dex_file->GetOatDexFile()->GetOatFile()->GetBssGcRoots().empty()) {
- mirror::ClassLoader* class_loader = outer_method->GetClassLoader();
- if (class_loader != nullptr) {
- DCHECK(!class_loader->GetClassTable()->InsertOatFile(dex_file->GetOatDexFile()->GetOatFile()))
+ ObjPtr<mirror::ClassLoader> class_loader = outer_method->GetClassLoader();
+ if (kIsDebugBuild) {
+ ClassTable* class_table =
+ Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(class_loader);
+ CHECK(class_table != nullptr &&
+ !class_table->InsertOatFile(dex_file->GetOatDexFile()->GetOatFile()))
<< "Oat file with .bss GC roots was not registered in class table: "
<< dex_file->GetOatDexFile()->GetOatFile()->GetLocation();
+ }
+ if (class_loader != nullptr) {
// Note that we emit the barrier before the compiled code stores the String or Class
// as a GC root. This is OK as there is no suspend point point in between.
Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader);
diff --git a/runtime/gc/accounting/mod_union_table.cc b/runtime/gc/accounting/mod_union_table.cc
index 0325535..a5bb91a 100644
--- a/runtime/gc/accounting/mod_union_table.cc
+++ b/runtime/gc/accounting/mod_union_table.cc
@@ -327,7 +327,7 @@
class EmptyMarkObjectVisitor : public MarkObjectVisitor {
public:
mirror::Object* MarkObject(mirror::Object* obj) OVERRIDE {return obj;}
- void MarkHeapReference(mirror::HeapReference<mirror::Object>*) OVERRIDE {}
+ void MarkHeapReference(mirror::HeapReference<mirror::Object>*, bool) OVERRIDE {}
};
void ModUnionTable::FilterCards() {
@@ -459,7 +459,7 @@
for (mirror::HeapReference<mirror::Object>* obj_ptr : references) {
if (obj_ptr->AsMirrorPtr() != nullptr) {
all_null = false;
- visitor->MarkHeapReference(obj_ptr);
+ visitor->MarkHeapReference(obj_ptr, /*do_atomic_update*/ false);
}
}
count += references.size();
diff --git a/runtime/gc/accounting/mod_union_table_test.cc b/runtime/gc/accounting/mod_union_table_test.cc
index cf63b30..48a8742 100644
--- a/runtime/gc/accounting/mod_union_table_test.cc
+++ b/runtime/gc/accounting/mod_union_table_test.cc
@@ -97,7 +97,8 @@
class CollectVisitedVisitor : public MarkObjectVisitor {
public:
explicit CollectVisitedVisitor(std::set<mirror::Object*>* out) : out_(out) {}
- virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* ref) OVERRIDE
+ virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* ref,
+ bool do_atomic_update ATTRIBUTE_UNUSED) OVERRIDE
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(ref != nullptr);
MarkObject(ref->AsMirrorPtr());
diff --git a/runtime/gc/accounting/remembered_set.cc b/runtime/gc/accounting/remembered_set.cc
index 29bab01..7b1e2b8 100644
--- a/runtime/gc/accounting/remembered_set.cc
+++ b/runtime/gc/accounting/remembered_set.cc
@@ -74,7 +74,7 @@
mirror::HeapReference<mirror::Object>* ref_ptr = obj->GetFieldObjectReferenceAddr(offset);
if (target_space_->HasAddress(ref_ptr->AsMirrorPtr())) {
*contains_reference_to_target_space_ = true;
- collector_->MarkHeapReference(ref_ptr);
+ collector_->MarkHeapReference(ref_ptr, /*do_atomic_update*/ false);
DCHECK(!target_space_->HasAddress(ref_ptr->AsMirrorPtr()));
}
}
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index f18ffb4..8f9c187 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -53,6 +53,8 @@
// Slow path mark stack size, increase this if the stack is getting full and it is causing
// performance problems.
static constexpr size_t kReadBarrierMarkStackSize = 512 * KB;
+// Verify that there are no missing card marks.
+static constexpr bool kVerifyNoMissingCardMarks = kIsDebugBuild;
ConcurrentCopying::ConcurrentCopying(Heap* heap,
const std::string& name_prefix,
@@ -109,12 +111,29 @@
}
}
-void ConcurrentCopying::MarkHeapReference(mirror::HeapReference<mirror::Object>* from_ref) {
- // Used for preserving soft references, should be OK to not have a CAS here since there should be
- // no other threads which can trigger read barriers on the same referent during reference
- // processing.
- from_ref->Assign(Mark(from_ref->AsMirrorPtr()));
- DCHECK(!from_ref->IsNull());
+void ConcurrentCopying::MarkHeapReference(mirror::HeapReference<mirror::Object>* field,
+ bool do_atomic_update) {
+ if (UNLIKELY(do_atomic_update)) {
+ // Used to mark the referent in DelayReferenceReferent in transaction mode.
+ mirror::Object* from_ref = field->AsMirrorPtr();
+ if (from_ref == nullptr) {
+ return;
+ }
+ mirror::Object* to_ref = Mark(from_ref);
+ if (from_ref != to_ref) {
+ do {
+ if (field->AsMirrorPtr() != from_ref) {
+ // Concurrently overwritten by a mutator.
+ break;
+ }
+ } while (!field->CasWeakRelaxed(from_ref, to_ref));
+ }
+ } else {
+ // Used for preserving soft references, should be OK to not have a CAS here since there should be
+ // no other threads which can trigger read barriers on the same referent during reference
+ // processing.
+ field->Assign(Mark(field->AsMirrorPtr()));
+ }
}
ConcurrentCopying::~ConcurrentCopying() {
@@ -138,7 +157,7 @@
MarkingPhase();
}
// Verify no from space refs. This causes a pause.
- if (kEnableNoFromSpaceRefsVerification || kIsDebugBuild) {
+ if (kEnableNoFromSpaceRefsVerification) {
TimingLogger::ScopedTiming split("(Paused)VerifyNoFromSpaceReferences", GetTimings());
ScopedPause pause(this, false);
CheckEmptyMarkStack();
@@ -318,6 +337,9 @@
TimingLogger::ScopedTiming split("(Paused)FlipCallback", cc->GetTimings());
// Note: self is not necessarily equal to thread since thread may be suspended.
Thread* self = Thread::Current();
+ if (kVerifyNoMissingCardMarks) {
+ cc->VerifyNoMissingCardMarks();
+ }
CHECK(thread == self);
Locks::mutator_lock_->AssertExclusiveHeld(self);
cc->region_space_->SetFromSpace(cc->rb_table_, cc->force_evacuate_all_);
@@ -428,6 +450,72 @@
}
}
+class ConcurrentCopying::VerifyNoMissingCardMarkVisitor {
+ public:
+ VerifyNoMissingCardMarkVisitor(ConcurrentCopying* cc, ObjPtr<mirror::Object> holder)
+ : cc_(cc),
+ holder_(holder) {}
+
+ void operator()(ObjPtr<mirror::Object> obj,
+ MemberOffset offset,
+ bool is_static ATTRIBUTE_UNUSED) const
+ REQUIRES_SHARED(Locks::mutator_lock_) ALWAYS_INLINE {
+ if (offset.Uint32Value() != mirror::Object::ClassOffset().Uint32Value()) {
+ CheckReference(obj->GetFieldObject<mirror::Object, kDefaultVerifyFlags, kWithoutReadBarrier>(
+ offset), offset.Uint32Value());
+ }
+ }
+ void operator()(ObjPtr<mirror::Class> klass,
+ ObjPtr<mirror::Reference> ref) const
+ REQUIRES_SHARED(Locks::mutator_lock_) ALWAYS_INLINE {
+ CHECK(klass->IsTypeOfReferenceClass());
+ this->operator()(ref, mirror::Reference::ReferentOffset(), false);
+ }
+
+ void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (!root->IsNull()) {
+ VisitRoot(root);
+ }
+ }
+
+ void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ CheckReference(root->AsMirrorPtr());
+ }
+
+ void CheckReference(mirror::Object* ref, int32_t offset = -1) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ CHECK(ref == nullptr || !cc_->region_space_->IsInNewlyAllocatedRegion(ref))
+ << holder_->PrettyTypeOf() << "(" << holder_.Ptr() << ") references object "
+ << ref->PrettyTypeOf() << "(" << ref << ") in newly allocated region at offset=" << offset;
+ }
+
+ private:
+ ConcurrentCopying* const cc_;
+ ObjPtr<mirror::Object> const holder_;
+};
+
+void ConcurrentCopying::VerifyNoMissingCardMarkCallback(mirror::Object* obj, void* arg) {
+ auto* collector = reinterpret_cast<ConcurrentCopying*>(arg);
+ // Objects not on dirty cards should never have references to newly allocated regions.
+ if (!collector->heap_->GetCardTable()->IsDirty(obj)) {
+ VerifyNoMissingCardMarkVisitor visitor(collector, /*holder*/ obj);
+ obj->VisitReferences</*kVisitNativeRoots*/true, kVerifyNone, kWithoutReadBarrier>(
+ visitor,
+ visitor);
+ }
+}
+
+void ConcurrentCopying::VerifyNoMissingCardMarks() {
+ TimingLogger::ScopedTiming split(__FUNCTION__, GetTimings());
+ region_space_->Walk(&VerifyNoMissingCardMarkCallback, this);
+ {
+ ReaderMutexLock rmu(Thread::Current(), *Locks::heap_bitmap_lock_);
+ heap_->GetLiveBitmap()->Walk(&VerifyNoMissingCardMarkCallback, this);
+ }
+}
+
// Switch threads that from from-space to to-space refs. Forward/mark the thread roots.
void ConcurrentCopying::FlipThreadRoots() {
TimingLogger::ScopedTiming split("FlipThreadRoots", GetTimings());
@@ -1380,7 +1468,7 @@
size_t alloc_size = RoundUp(obj_size, space::RegionSpace::kAlignment);
region_space_->AddLiveBytes(to_ref, alloc_size);
}
- if (ReadBarrier::kEnableToSpaceInvariantChecks || kIsDebugBuild) {
+ if (ReadBarrier::kEnableToSpaceInvariantChecks) {
AssertToSpaceInvariantObjectVisitor visitor(this);
visitor(to_ref);
}
@@ -2330,7 +2418,9 @@
MutexLock mu(self, mark_stack_lock_);
CHECK_EQ(pooled_mark_stacks_.size(), kMarkStackPoolSize);
}
- {
+ // kVerifyNoMissingCardMarks relies on the region space cards not being cleared to avoid false
+ // positives.
+ if (!kVerifyNoMissingCardMarks) {
TimingLogger::ScopedTiming split("ClearRegionSpaceCards", GetTimings());
// We do not currently use the region space cards at all, madvise them away to save ram.
heap_->GetCardTable()->ClearCardRange(region_space_->Begin(), region_space_->Limit());
diff --git a/runtime/gc/collector/concurrent_copying.h b/runtime/gc/collector/concurrent_copying.h
index 844bb45..a0da9fc 100644
--- a/runtime/gc/collector/concurrent_copying.h
+++ b/runtime/gc/collector/concurrent_copying.h
@@ -162,6 +162,12 @@
void VerifyGrayImmuneObjects()
REQUIRES(Locks::mutator_lock_)
REQUIRES(!mark_stack_lock_);
+ static void VerifyNoMissingCardMarkCallback(mirror::Object* obj, void* arg)
+ REQUIRES(Locks::mutator_lock_)
+ REQUIRES(!mark_stack_lock_);
+ void VerifyNoMissingCardMarks()
+ REQUIRES(Locks::mutator_lock_)
+ REQUIRES(!mark_stack_lock_);
size_t ProcessThreadLocalMarkStacks(bool disable_weak_ref_access, Closure* checkpoint_callback)
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!mark_stack_lock_);
void RevokeThreadLocalMarkStacks(bool disable_weak_ref_access, Closure* checkpoint_callback)
@@ -176,7 +182,8 @@
virtual mirror::Object* MarkObject(mirror::Object* from_ref) OVERRIDE
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!mark_stack_lock_, !skipped_blocks_lock_, !immune_gray_stack_lock_);
- virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* from_ref) OVERRIDE
+ virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* from_ref,
+ bool do_atomic_update) OVERRIDE
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!mark_stack_lock_, !skipped_blocks_lock_, !immune_gray_stack_lock_);
virtual mirror::Object* IsMarked(mirror::Object* from_ref) OVERRIDE
@@ -329,6 +336,7 @@
class VerifyNoFromSpaceRefsFieldVisitor;
class VerifyNoFromSpaceRefsObjectVisitor;
class VerifyNoFromSpaceRefsVisitor;
+ class VerifyNoMissingCardMarkVisitor;
DISALLOW_IMPLICIT_CONSTRUCTORS(ConcurrentCopying);
};
diff --git a/runtime/gc/collector/garbage_collector.cc b/runtime/gc/collector/garbage_collector.cc
index 14fd332..1e4196b 100644
--- a/runtime/gc/collector/garbage_collector.cc
+++ b/runtime/gc/collector/garbage_collector.cc
@@ -65,7 +65,8 @@
name_(name),
pause_histogram_((name_ + " paused").c_str(), kPauseBucketSize, kPauseBucketCount),
cumulative_timings_(name),
- pause_histogram_lock_("pause histogram lock", kDefaultMutexLevel, true) {
+ pause_histogram_lock_("pause histogram lock", kDefaultMutexLevel, true),
+ is_transaction_active_(false) {
ResetCumulativeStatistics();
}
@@ -88,6 +89,9 @@
uint64_t start_time = NanoTime();
Iteration* current_iteration = GetCurrentIteration();
current_iteration->Reset(gc_cause, clear_soft_references);
+ // Note transaction mode is single-threaded and there's no asynchronous GC and this flag doesn't
+ // change in the middle of a GC.
+ is_transaction_active_ = Runtime::Current()->IsActiveTransaction();
RunPhases(); // Run all the GC phases.
// Add the current timings to the cumulative timings.
cumulative_timings_.AddLogger(*GetTimings());
@@ -109,6 +113,7 @@
MutexLock mu(self, pause_histogram_lock_);
pause_histogram_.AdjustAndAddValue(pause_time);
}
+ is_transaction_active_ = false;
}
void GarbageCollector::SwapBitmaps() {
diff --git a/runtime/gc/collector/garbage_collector.h b/runtime/gc/collector/garbage_collector.h
index 95601d7..14d0499 100644
--- a/runtime/gc/collector/garbage_collector.h
+++ b/runtime/gc/collector/garbage_collector.h
@@ -199,12 +199,17 @@
// Force mark an object.
virtual mirror::Object* MarkObject(mirror::Object* obj)
REQUIRES_SHARED(Locks::mutator_lock_) = 0;
- virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj)
+ virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj,
+ bool do_atomic_update)
REQUIRES_SHARED(Locks::mutator_lock_) = 0;
virtual void DelayReferenceReferent(ObjPtr<mirror::Class> klass,
ObjPtr<mirror::Reference> reference)
REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+ bool IsTransactionActive() const {
+ return is_transaction_active_;
+ }
+
protected:
// Run all of the GC phases.
virtual void RunPhases() = 0;
@@ -223,6 +228,7 @@
int64_t total_freed_bytes_;
CumulativeLogger cumulative_timings_;
mutable Mutex pause_histogram_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+ bool is_transaction_active_;
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(GarbageCollector);
diff --git a/runtime/gc/collector/mark_compact.cc b/runtime/gc/collector/mark_compact.cc
index 85e6783..0039388 100644
--- a/runtime/gc/collector/mark_compact.cc
+++ b/runtime/gc/collector/mark_compact.cc
@@ -260,7 +260,8 @@
mark_stack_->PushBack(obj);
}
-void MarkCompact::MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr) {
+void MarkCompact::MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr,
+ bool do_atomic_update ATTRIBUTE_UNUSED) {
if (updating_references_) {
UpdateHeapReference(obj_ptr);
} else {
diff --git a/runtime/gc/collector/mark_compact.h b/runtime/gc/collector/mark_compact.h
index 6d52d5d..85727c2 100644
--- a/runtime/gc/collector/mark_compact.h
+++ b/runtime/gc/collector/mark_compact.h
@@ -170,7 +170,8 @@
// Mark a single object.
virtual mirror::Object* MarkObject(mirror::Object* obj) OVERRIDE
REQUIRES(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
- virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr) OVERRIDE
+ virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr,
+ bool do_atomic_update) OVERRIDE
REQUIRES(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
virtual mirror::Object* IsMarked(mirror::Object* obj) OVERRIDE
REQUIRES_SHARED(Locks::heap_bitmap_lock_)
diff --git a/runtime/gc/collector/mark_sweep.cc b/runtime/gc/collector/mark_sweep.cc
index f00da73..f591cf0 100644
--- a/runtime/gc/collector/mark_sweep.cc
+++ b/runtime/gc/collector/mark_sweep.cc
@@ -532,7 +532,8 @@
return !mark_bitmap_->AtomicTestAndSet(obj, visitor);
}
-void MarkSweep::MarkHeapReference(mirror::HeapReference<mirror::Object>* ref) {
+void MarkSweep::MarkHeapReference(mirror::HeapReference<mirror::Object>* ref,
+ bool do_atomic_update ATTRIBUTE_UNUSED) {
MarkObject(ref->AsMirrorPtr(), nullptr, MemberOffset(0));
}
diff --git a/runtime/gc/collector/mark_sweep.h b/runtime/gc/collector/mark_sweep.h
index a6e2d61..5a9b9f8 100644
--- a/runtime/gc/collector/mark_sweep.h
+++ b/runtime/gc/collector/mark_sweep.h
@@ -216,7 +216,8 @@
REQUIRES(!mark_stack_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
- virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* ref) OVERRIDE
+ virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* ref,
+ bool do_atomic_update) OVERRIDE
REQUIRES(Locks::heap_bitmap_lock_)
REQUIRES(!mark_stack_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/gc/collector/semi_space.cc b/runtime/gc/collector/semi_space.cc
index cb9e7e2..4c0f317 100644
--- a/runtime/gc/collector/semi_space.cc
+++ b/runtime/gc/collector/semi_space.cc
@@ -606,7 +606,8 @@
return ref.AsMirrorPtr();
}
-void SemiSpace::MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr) {
+void SemiSpace::MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr,
+ bool do_atomic_update ATTRIBUTE_UNUSED) {
MarkObject(obj_ptr);
}
diff --git a/runtime/gc/collector/semi_space.h b/runtime/gc/collector/semi_space.h
index 52b5e5f..9d6e74d 100644
--- a/runtime/gc/collector/semi_space.h
+++ b/runtime/gc/collector/semi_space.h
@@ -110,7 +110,8 @@
virtual mirror::Object* MarkObject(mirror::Object* root) OVERRIDE
REQUIRES(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
- virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr) OVERRIDE
+ virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr,
+ bool do_atomic_update) OVERRIDE
REQUIRES(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
void ScanObject(mirror::Object* obj)
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 34afa2a..53be30e 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -3332,7 +3332,7 @@
virtual mirror::Object* MarkObject(mirror::Object* obj) OVERRIDE {
return obj;
}
- virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>*) OVERRIDE {
+ virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>*, bool) OVERRIDE {
}
};
diff --git a/runtime/gc/reference_processor.cc b/runtime/gc/reference_processor.cc
index 86b1522..65a550e 100644
--- a/runtime/gc/reference_processor.cc
+++ b/runtime/gc/reference_processor.cc
@@ -139,6 +139,14 @@
CHECK_EQ(!self->GetWeakRefAccessEnabled(), concurrent);
}
}
+ if (kIsDebugBuild && collector->IsTransactionActive()) {
+ // In transaction mode, we shouldn't enqueue any Reference to the queues.
+ // See DelayReferenceReferent().
+ DCHECK(soft_reference_queue_.IsEmpty());
+ DCHECK(weak_reference_queue_.IsEmpty());
+ DCHECK(finalizer_reference_queue_.IsEmpty());
+ DCHECK(phantom_reference_queue_.IsEmpty());
+ }
// Unless required to clear soft references with white references, preserve some white referents.
if (!clear_soft_references) {
TimingLogger::ScopedTiming split(concurrent ? "ForwardSoftReferences" :
@@ -206,6 +214,15 @@
// do_atomic_update needs to be true because this happens outside of the reference processing
// phase.
if (!collector->IsNullOrMarkedHeapReference(referent, /*do_atomic_update*/true)) {
+ if (UNLIKELY(collector->IsTransactionActive())) {
+ // In transaction mode, keep the referent alive and avoid any reference processing to avoid the
+ // issue of rolling back reference processing. do_atomic_update needs to be true because this
+ // happens outside of the reference processing phase.
+ if (!referent->IsNull()) {
+ collector->MarkHeapReference(referent, /*do_atomic_update*/ true);
+ }
+ return;
+ }
Thread* self = Thread::Current();
// TODO: Remove these locks, and use atomic stacks for storing references?
// We need to check that the references haven't already been enqueued since we can end up
diff --git a/runtime/gc/reference_queue.cc b/runtime/gc/reference_queue.cc
index 734caea..fd5dcf9 100644
--- a/runtime/gc/reference_queue.cc
+++ b/runtime/gc/reference_queue.cc
@@ -67,6 +67,11 @@
list_->SetPendingNext(next);
}
ref->SetPendingNext(nullptr);
+ return ref;
+}
+
+// This must be called whenever DequeuePendingReference is called.
+void ReferenceQueue::DisableReadBarrierForReference(ObjPtr<mirror::Reference> ref) {
Heap* heap = Runtime::Current()->GetHeap();
if (kUseBakerOrBrooksReadBarrier && heap->CurrentCollectorType() == kCollectorTypeCC &&
heap->ConcurrentCopyingCollector()->IsActive()) {
@@ -92,7 +97,6 @@
}
}
}
- return ref;
}
void ReferenceQueue::Dump(std::ostream& os) const {
@@ -140,6 +144,9 @@
}
cleared_references->EnqueueReference(ref);
}
+ // Delay disabling the read barrier until here so that the ClearReferent call above in
+ // transaction mode will trigger the read barrier.
+ DisableReadBarrierForReference(ref);
}
}
@@ -162,6 +169,9 @@
}
cleared_references->EnqueueReference(ref);
}
+ // Delay disabling the read barrier until here so that the ClearReferent call above in
+ // transaction mode will trigger the read barrier.
+ DisableReadBarrierForReference(ref->AsReference());
}
}
@@ -174,7 +184,9 @@
do {
mirror::HeapReference<mirror::Object>* referent_addr = ref->GetReferentReferenceAddr();
if (referent_addr->AsMirrorPtr() != nullptr) {
- visitor->MarkHeapReference(referent_addr);
+ // do_atomic_update is false because mutators can't access the referent due to the weak ref
+ // access blocking.
+ visitor->MarkHeapReference(referent_addr, /*do_atomic_update*/ false);
}
ref = ref->GetPendingNext();
} while (LIKELY(ref != head));
diff --git a/runtime/gc/reference_queue.h b/runtime/gc/reference_queue.h
index b5ec1e5..b73a880 100644
--- a/runtime/gc/reference_queue.h
+++ b/runtime/gc/reference_queue.h
@@ -63,8 +63,15 @@
void EnqueueReference(ObjPtr<mirror::Reference> ref) REQUIRES_SHARED(Locks::mutator_lock_);
// Dequeue a reference from the queue and return that dequeued reference.
+ // Call DisableReadBarrierForReference for the reference that's returned from this function.
ObjPtr<mirror::Reference> DequeuePendingReference() REQUIRES_SHARED(Locks::mutator_lock_);
+ // If applicable, disable the read barrier for the reference after its referent is handled (see
+ // ConcurrentCopying::ProcessMarkStackRef.) This must be called for a reference that's dequeued
+ // from pending queue (DequeuePendingReference).
+ void DisableReadBarrierForReference(ObjPtr<mirror::Reference> ref)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
// Enqueues finalizer references with white referents. White referents are blackened, moved to
// the zombie field, and the referent field is cleared.
void EnqueueFinalizerReferences(ReferenceQueue* cleared_references,
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 2163a20..010ef11 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -1237,9 +1237,9 @@
}
dex_cache->FixupStrings<kWithoutReadBarrier>(new_strings, fixup_adapter);
}
- GcRoot<mirror::Class>* types = dex_cache->GetResolvedTypes();
+ mirror::TypeDexCacheType* types = dex_cache->GetResolvedTypes();
if (types != nullptr) {
- GcRoot<mirror::Class>* new_types = fixup_adapter.ForwardObject(types);
+ mirror::TypeDexCacheType* new_types = fixup_adapter.ForwardObject(types);
if (types != new_types) {
dex_cache->SetResolvedTypes(new_types);
}
diff --git a/runtime/gc/space/region_space.h b/runtime/gc/space/region_space.h
index f3b9595..feab9b0 100644
--- a/runtime/gc/space/region_space.h
+++ b/runtime/gc/space/region_space.h
@@ -176,6 +176,14 @@
return false;
}
+ bool IsInNewlyAllocatedRegion(mirror::Object* ref) {
+ if (HasAddress(ref)) {
+ Region* r = RefToRegionUnlocked(ref);
+ return r->IsNewlyAllocated();
+ }
+ return false;
+ }
+
bool IsInUnevacFromSpace(mirror::Object* ref) {
if (HasAddress(ref)) {
Region* r = RefToRegionUnlocked(ref);
@@ -351,6 +359,10 @@
return idx_;
}
+ bool IsNewlyAllocated() const {
+ return is_newly_allocated_;
+ }
+
bool IsInFromSpace() const {
return type_ == RegionType::kRegionTypeFromSpace;
}
diff --git a/runtime/gc_root-inl.h b/runtime/gc_root-inl.h
index 390ed3c..7795c66 100644
--- a/runtime/gc_root-inl.h
+++ b/runtime/gc_root-inl.h
@@ -38,7 +38,7 @@
: root_(mirror::CompressedReference<mirror::Object>::FromMirrorPtr(ref)) { }
template<class MirrorType>
-inline GcRoot<MirrorType>::GcRoot(ObjPtr<MirrorType, kIsDebugBuild> ref)
+inline GcRoot<MirrorType>::GcRoot(ObjPtr<MirrorType> ref)
: GcRoot(ref.Ptr()) { }
inline std::string RootInfo::ToString() const {
diff --git a/runtime/gc_root.h b/runtime/gc_root.h
index 79e80f1..0894e9b 100644
--- a/runtime/gc_root.h
+++ b/runtime/gc_root.h
@@ -24,7 +24,7 @@
namespace art {
class ArtField;
class ArtMethod;
-template<class MirrorType, bool kPoison> class ObjPtr;
+template<class MirrorType> class ObjPtr;
namespace mirror {
class Object;
@@ -215,7 +215,7 @@
ALWAYS_INLINE GcRoot() {}
explicit ALWAYS_INLINE GcRoot(MirrorType* ref)
REQUIRES_SHARED(Locks::mutator_lock_);
- explicit ALWAYS_INLINE GcRoot(ObjPtr<MirrorType, kIsDebugBuild> ref)
+ explicit ALWAYS_INLINE GcRoot(ObjPtr<MirrorType> ref)
REQUIRES_SHARED(Locks::mutator_lock_);
private:
diff --git a/runtime/handle_scope-inl.h b/runtime/handle_scope-inl.h
index 077f45e..492d4b4 100644
--- a/runtime/handle_scope-inl.h
+++ b/runtime/handle_scope-inl.h
@@ -114,9 +114,9 @@
return h;
}
-template<size_t kNumReferences> template<class MirrorType, bool kPoison>
+template<size_t kNumReferences> template<class MirrorType>
inline MutableHandle<MirrorType> FixedSizeHandleScope<kNumReferences>::NewHandle(
- ObjPtr<MirrorType, kPoison> object) {
+ ObjPtr<MirrorType> object) {
return NewHandle(object.Ptr());
}
@@ -191,9 +191,8 @@
return current_scope_->NewHandle(object);
}
-template<class MirrorType, bool kPoison>
-inline MutableHandle<MirrorType> VariableSizedHandleScope::NewHandle(
- ObjPtr<MirrorType, kPoison> ptr) {
+template<class MirrorType>
+inline MutableHandle<MirrorType> VariableSizedHandleScope::NewHandle(ObjPtr<MirrorType> ptr) {
return NewHandle(ptr.Ptr());
}
diff --git a/runtime/handle_scope.h b/runtime/handle_scope.h
index adb7d8a..c43a482 100644
--- a/runtime/handle_scope.h
+++ b/runtime/handle_scope.h
@@ -30,7 +30,7 @@
namespace art {
class HandleScope;
-template<class MirrorType, bool kPoison> class ObjPtr;
+template<class MirrorType> class ObjPtr;
class Thread;
class VariableSizedHandleScope;
@@ -224,8 +224,8 @@
ALWAYS_INLINE HandleWrapperObjPtr<T> NewHandleWrapper(ObjPtr<T>* object)
REQUIRES_SHARED(Locks::mutator_lock_);
- template<class MirrorType, bool kPoison>
- ALWAYS_INLINE MutableHandle<MirrorType> NewHandle(ObjPtr<MirrorType, kPoison> object)
+ template<class MirrorType>
+ ALWAYS_INLINE MutableHandle<MirrorType> NewHandle(ObjPtr<MirrorType> object)
REQUIRES_SHARED(Locks::mutator_lock_);
ALWAYS_INLINE void SetReference(size_t i, mirror::Object* object)
@@ -250,7 +250,7 @@
StackReference<mirror::Object> storage_[kNumReferences];
// Position new handles will be created.
- size_t pos_ = 0;
+ uint32_t pos_ = 0;
template<size_t kNumRefs> friend class StackHandleScope;
friend class VariableSizedHandleScope;
@@ -286,8 +286,8 @@
template<class T>
MutableHandle<T> NewHandle(T* object) REQUIRES_SHARED(Locks::mutator_lock_);
- template<class MirrorType, bool kPoison>
- MutableHandle<MirrorType> NewHandle(ObjPtr<MirrorType, kPoison> ptr)
+ template<class MirrorType>
+ MutableHandle<MirrorType> NewHandle(ObjPtr<MirrorType> ptr)
REQUIRES_SHARED(Locks::mutator_lock_);
// Number of references contained within this handle scope.
@@ -299,12 +299,20 @@
void VisitRoots(Visitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_);
private:
- static constexpr size_t kNumReferencesPerScope = 4;
+ static constexpr size_t kLocalScopeSize = 64u;
+ static constexpr size_t kSizeOfReferencesPerScope =
+ kLocalScopeSize
+ - /* BaseHandleScope::link_ */ sizeof(BaseHandleScope*)
+ - /* BaseHandleScope::number_of_references_ */ sizeof(int32_t)
+ - /* FixedSizeHandleScope<>::pos_ */ sizeof(uint32_t);
+ static constexpr size_t kNumReferencesPerScope =
+ kSizeOfReferencesPerScope / sizeof(StackReference<mirror::Object>);
Thread* const self_;
// Linked list of fixed size handle scopes.
using LocalScopeType = FixedSizeHandleScope<kNumReferencesPerScope>;
+ static_assert(sizeof(LocalScopeType) == kLocalScopeSize, "Unexpected size of LocalScopeType");
LocalScopeType* current_scope_;
DISALLOW_COPY_AND_ASSIGN(VariableSizedHandleScope);
diff --git a/runtime/image.cc b/runtime/image.cc
index 54b099e..4e6da79 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -25,7 +25,7 @@
namespace art {
const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' };
-const uint8_t ImageHeader::kImageVersion[] = { '0', '3', '6', '\0' }; // Erroneous resolved class.
+const uint8_t ImageHeader::kImageVersion[] = { '0', '3', '8', '\0' }; // hash-based DexCache types
ImageHeader::ImageHeader(uint32_t image_begin,
uint32_t image_size,
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index af0478c..80554c2 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -1330,17 +1330,18 @@
result->SetC(string->CharAt(index));
}
-// This allows setting chars from the new style of String objects during compilation.
-void UnstartedRuntime::UnstartedStringSetCharAt(
- Thread* self, ShadowFrame* shadow_frame, JValue* result ATTRIBUTE_UNUSED, size_t arg_offset) {
- jint index = shadow_frame->GetVReg(arg_offset + 1);
- jchar c = shadow_frame->GetVReg(arg_offset + 2);
- mirror::String* string = shadow_frame->GetVRegReference(arg_offset)->AsString();
+// This allows creating String objects with replaced characters during compilation.
+// String.doReplace(char, char) is called from String.replace(char, char) when there is a match.
+void UnstartedRuntime::UnstartedStringDoReplace(
+ Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
+ jchar old_c = shadow_frame->GetVReg(arg_offset + 1);
+ jchar new_c = shadow_frame->GetVReg(arg_offset + 2);
+ ObjPtr<mirror::String> string = shadow_frame->GetVRegReference(arg_offset)->AsString();
if (string == nullptr) {
- AbortTransactionOrFail(self, "String.setCharAt with null object");
+ AbortTransactionOrFail(self, "String.replaceWithMatch with null object");
return;
}
- string->SetCharAt(index, c);
+ result->SetL(string->DoReplace(self, old_c, new_c));
}
// This allows creating the new style of String objects during compilation.
diff --git a/runtime/interpreter/unstarted_runtime_list.h b/runtime/interpreter/unstarted_runtime_list.h
index 6fc7989..e9435e4 100644
--- a/runtime/interpreter/unstarted_runtime_list.h
+++ b/runtime/interpreter/unstarted_runtime_list.h
@@ -63,7 +63,7 @@
V(RuntimeAvailableProcessors, "int java.lang.Runtime.availableProcessors()") \
V(StringGetCharsNoCheck, "void java.lang.String.getCharsNoCheck(int, int, char[], int)") \
V(StringCharAt, "char java.lang.String.charAt(int)") \
- V(StringSetCharAt, "void java.lang.String.setCharAt(int, char)") \
+ V(StringDoReplace, "java.lang.String java.lang.String.doReplace(char, char)") \
V(StringFactoryNewStringFromChars, "java.lang.String java.lang.StringFactory.newStringFromChars(int, int, char[])") \
V(StringFactoryNewStringFromString, "java.lang.String java.lang.StringFactory.newStringFromString(java.lang.String)") \
V(StringFastSubstring, "java.lang.String java.lang.String.fastSubstring(int, int)") \
diff --git a/runtime/jdwp/jdwp.h b/runtime/jdwp/jdwp.h
index 86af6d4..af29468 100644
--- a/runtime/jdwp/jdwp.h
+++ b/runtime/jdwp/jdwp.h
@@ -203,7 +203,8 @@
*/
void PostLocationEvent(const EventLocation* pLoc, mirror::Object* thisPtr, int eventFlags,
const JValue* returnValue)
- REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+ REQUIRES(!Locks::jdwp_event_list_lock_, !jdwp_token_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_);
/*
* A field of interest has been accessed or modified. This is used for field access and field
@@ -214,7 +215,8 @@
*/
void PostFieldEvent(const EventLocation* pLoc, ArtField* field, mirror::Object* thisPtr,
const JValue* fieldValue, bool is_modification)
- REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+ REQUIRES(!Locks::jdwp_event_list_lock_, !jdwp_token_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_);
/*
* An exception has been thrown.
@@ -223,19 +225,22 @@
*/
void PostException(const EventLocation* pThrowLoc, mirror::Throwable* exception_object,
const EventLocation* pCatchLoc, mirror::Object* thisPtr)
- REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+ REQUIRES(!Locks::jdwp_event_list_lock_, !jdwp_token_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_);
/*
* A thread has started or stopped.
*/
void PostThreadChange(Thread* thread, bool start)
- REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+ REQUIRES(!Locks::jdwp_event_list_lock_, !jdwp_token_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_);
/*
* Class has been prepared.
*/
void PostClassPrepare(mirror::Class* klass)
- REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+ REQUIRES(!Locks::jdwp_event_list_lock_, !jdwp_token_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_);
/*
* The VM is about to stop.
@@ -259,7 +264,7 @@
void SendRequest(ExpandBuf* pReq);
void ResetState()
- REQUIRES(!event_list_lock_)
+ REQUIRES(!Locks::jdwp_event_list_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
/* atomic ops to get next serial number */
@@ -268,7 +273,7 @@
void Run()
REQUIRES(!Locks::mutator_lock_, !Locks::thread_suspend_count_lock_, !thread_start_lock_,
- !attach_lock_, !event_list_lock_);
+ !attach_lock_, !Locks::jdwp_event_list_lock_);
/*
* Register an event by adding it to the event list.
@@ -277,25 +282,25 @@
* may discard its pointer after calling this.
*/
JdwpError RegisterEvent(JdwpEvent* pEvent)
- REQUIRES(!event_list_lock_)
+ REQUIRES(!Locks::jdwp_event_list_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
/*
* Unregister an event, given the requestId.
*/
void UnregisterEventById(uint32_t requestId)
- REQUIRES(!event_list_lock_)
+ REQUIRES(!Locks::jdwp_event_list_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
void UnregisterLocationEventsOnClass(ObjPtr<mirror::Class> klass)
- REQUIRES(!event_list_lock_)
+ REQUIRES(!Locks::jdwp_event_list_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
/*
* Unregister all events.
*/
void UnregisterAll()
- REQUIRES(!event_list_lock_)
+ REQUIRES(!Locks::jdwp_event_list_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
private:
@@ -310,16 +315,16 @@
ObjectId threadId)
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!jdwp_token_lock_);
void CleanupMatchList(const std::vector<JdwpEvent*>& match_list)
- REQUIRES(event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+ REQUIRES(Locks::jdwp_event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
void EventFinish(ExpandBuf* pReq);
bool FindMatchingEvents(JdwpEventKind eventKind, const ModBasket& basket,
std::vector<JdwpEvent*>* match_list)
- REQUIRES(!event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+ REQUIRES(!Locks::jdwp_event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
void FindMatchingEventsLocked(JdwpEventKind eventKind, const ModBasket& basket,
std::vector<JdwpEvent*>* match_list)
- REQUIRES(event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+ REQUIRES(Locks::jdwp_event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
void UnregisterEvent(JdwpEvent* pEvent)
- REQUIRES(event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+ REQUIRES(Locks::jdwp_event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
void SendBufferedRequest(uint32_t type, const std::vector<iovec>& iov);
/*
@@ -387,9 +392,8 @@
AtomicInteger event_serial_;
// Linked list of events requested by the debugger (breakpoints, class prep, etc).
- Mutex event_list_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER ACQUIRED_BEFORE(Locks::breakpoint_lock_);
- JdwpEvent* event_list_ GUARDED_BY(event_list_lock_);
- size_t event_list_size_ GUARDED_BY(event_list_lock_); // Number of elements in event_list_.
+ JdwpEvent* event_list_ GUARDED_BY(Locks::jdwp_event_list_lock_);
+ size_t event_list_size_ GUARDED_BY(Locks::jdwp_event_list_lock_); // Number of elements in event_list_.
// Used to synchronize JDWP command handler thread and event threads so only one
// thread does JDWP stuff at a time. This prevent from interleaving command handling
@@ -410,7 +414,7 @@
// When the runtime shuts down, it needs to stop JDWP command handler thread by closing the
// JDWP connection. However, if the JDWP thread is processing a command, it needs to wait
// for the command to finish so we can send its reply before closing the connection.
- Mutex shutdown_lock_ ACQUIRED_AFTER(event_list_lock_);
+ Mutex shutdown_lock_ ACQUIRED_AFTER(Locks::jdwp_event_list_lock_);
ConditionVariable shutdown_cond_ GUARDED_BY(shutdown_lock_);
bool processing_request_ GUARDED_BY(shutdown_lock_);
};
diff --git a/runtime/jdwp/jdwp_event.cc b/runtime/jdwp/jdwp_event.cc
index 96249f9..36d733e 100644
--- a/runtime/jdwp/jdwp_event.cc
+++ b/runtime/jdwp/jdwp_event.cc
@@ -237,7 +237,7 @@
/*
* Add to list.
*/
- MutexLock mu(Thread::Current(), event_list_lock_);
+ MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
if (event_list_ != nullptr) {
pEvent->next = event_list_;
event_list_->prev = pEvent;
@@ -256,7 +256,7 @@
StackHandleScope<1> hs(Thread::Current());
Handle<mirror::Class> h_klass(hs.NewHandle(klass));
std::vector<JdwpEvent*> to_remove;
- MutexLock mu(Thread::Current(), event_list_lock_);
+ MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
for (JdwpEvent* cur_event = event_list_; cur_event != nullptr; cur_event = cur_event->next) {
// Fill in the to_remove list
bool found_event = false;
@@ -356,7 +356,7 @@
void JdwpState::UnregisterEventById(uint32_t requestId) {
bool found = false;
{
- MutexLock mu(Thread::Current(), event_list_lock_);
+ MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
for (JdwpEvent* pEvent = event_list_; pEvent != nullptr; pEvent = pEvent->next) {
if (pEvent->requestId == requestId) {
@@ -383,7 +383,7 @@
* Remove all entries from the event list.
*/
void JdwpState::UnregisterAll() {
- MutexLock mu(Thread::Current(), event_list_lock_);
+ MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
JdwpEvent* pEvent = event_list_;
while (pEvent != nullptr) {
@@ -593,7 +593,7 @@
*/
bool JdwpState::FindMatchingEvents(JdwpEventKind event_kind, const ModBasket& basket,
std::vector<JdwpEvent*>* match_list) {
- MutexLock mu(Thread::Current(), event_list_lock_);
+ MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
match_list->reserve(event_list_size_);
FindMatchingEventsLocked(event_kind, basket, match_list);
return !match_list->empty();
@@ -908,7 +908,7 @@
std::vector<JdwpEvent*> match_list;
{
// We use the locked version because we have multiple possible match events.
- MutexLock mu(Thread::Current(), event_list_lock_);
+ MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
match_list.reserve(event_list_size_);
if ((eventFlags & Dbg::kBreakpoint) != 0) {
FindMatchingEventsLocked(EK_BREAKPOINT, basket, &match_list);
@@ -955,7 +955,7 @@
}
{
- MutexLock mu(Thread::Current(), event_list_lock_);
+ MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
CleanupMatchList(match_list);
}
@@ -1041,7 +1041,7 @@
}
{
- MutexLock mu(Thread::Current(), event_list_lock_);
+ MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
CleanupMatchList(match_list);
}
@@ -1103,7 +1103,7 @@
}
{
- MutexLock mu(Thread::Current(), event_list_lock_);
+ MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
CleanupMatchList(match_list);
}
@@ -1213,7 +1213,7 @@
}
{
- MutexLock mu(Thread::Current(), event_list_lock_);
+ MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
CleanupMatchList(match_list);
}
@@ -1295,7 +1295,7 @@
}
{
- MutexLock mu(Thread::Current(), event_list_lock_);
+ MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
CleanupMatchList(match_list);
}
diff --git a/runtime/jdwp/jdwp_main.cc b/runtime/jdwp/jdwp_main.cc
index 7707ba4..64ed724 100644
--- a/runtime/jdwp/jdwp_main.cc
+++ b/runtime/jdwp/jdwp_main.cc
@@ -227,7 +227,6 @@
last_activity_time_ms_(0),
request_serial_(0x10000000),
event_serial_(0x20000000),
- event_list_lock_("JDWP event list lock", kJdwpEventListLock),
event_list_(nullptr),
event_list_size_(0),
jdwp_token_lock_("JDWP token lock"),
@@ -331,7 +330,7 @@
UnregisterAll();
{
- MutexLock mu(Thread::Current(), event_list_lock_);
+ MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
CHECK(event_list_ == nullptr);
}
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 60ab275..b1ba952 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -1245,15 +1245,55 @@
}
void JitCodeCache::GetProfiledMethods(const std::set<std::string>& dex_base_locations,
- std::vector<MethodReference>& methods) {
+ std::vector<ProfileMethodInfo>& methods) {
ScopedTrace trace(__FUNCTION__);
MutexLock mu(Thread::Current(), lock_);
for (const ProfilingInfo* info : profiling_infos_) {
ArtMethod* method = info->GetMethod();
const DexFile* dex_file = method->GetDexFile();
- if (ContainsElement(dex_base_locations, dex_file->GetBaseLocation())) {
- methods.emplace_back(dex_file, method->GetDexMethodIndex());
+ if (!ContainsElement(dex_base_locations, dex_file->GetBaseLocation())) {
+ // Skip dex files which are not profiled.
+ continue;
}
+ std::vector<ProfileMethodInfo::ProfileInlineCache> inline_caches;
+ for (size_t i = 0; i < info->number_of_inline_caches_; ++i) {
+ std::vector<ProfileMethodInfo::ProfileClassReference> profile_classes;
+ const InlineCache& cache = info->cache_[i];
+ for (size_t k = 0; k < InlineCache::kIndividualCacheSize; k++) {
+ mirror::Class* cls = cache.classes_[k].Read();
+ if (cls == nullptr) {
+ break;
+ }
+
+ const DexFile* class_dex_file = nullptr;
+ dex::TypeIndex type_index;
+
+ if (cls->GetDexCache() == nullptr) {
+ DCHECK(cls->IsArrayClass()) << cls->PrettyClass();
+ class_dex_file = dex_file;
+ type_index = cls->FindTypeIndexInOtherDexFile(*dex_file);
+ } else {
+ class_dex_file = &(cls->GetDexFile());
+ type_index = cls->GetDexTypeIndex();
+ }
+ if (!type_index.IsValid()) {
+ // Could be a proxy class or an array for which we couldn't find the type index.
+ // TODO(calin): can we really miss the type index for arrays here?
+ continue;
+ }
+ if (ContainsElement(dex_base_locations, class_dex_file->GetBaseLocation())) {
+ // Only consider classes from the same apk (including multidex).
+ profile_classes.emplace_back(/*ProfileMethodInfo::ProfileClassReference*/
+ class_dex_file, type_index);
+ }
+ }
+ if (!profile_classes.empty()) {
+ inline_caches.emplace_back(/*ProfileMethodInfo::ProfileInlineCache*/
+ cache.dex_pc_, profile_classes);
+ }
+ }
+ methods.emplace_back(/*ProfileMethodInfo*/
+ dex_file, method->GetDexMethodIndex(), inline_caches);
}
}
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index b5e3176..33a792f 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -30,6 +30,7 @@
#include "method_reference.h"
#include "oat_file.h"
#include "object_callbacks.h"
+#include "profile_compilation_info.h"
#include "safe_map.h"
#include "thread_pool.h"
@@ -192,7 +193,7 @@
// Adds to `methods` all profiled methods which are part of any of the given dex locations.
void GetProfiledMethods(const std::set<std::string>& dex_base_locations,
- std::vector<MethodReference>& methods)
+ std::vector<ProfileMethodInfo>& methods)
REQUIRES(!lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc
index 54fc038..627cc93 100644
--- a/runtime/jit/profile_compilation_info.cc
+++ b/runtime/jit/profile_compilation_info.cc
@@ -37,7 +37,7 @@
namespace art {
const uint8_t ProfileCompilationInfo::kProfileMagic[] = { 'p', 'r', 'o', '\0' };
-const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '0', '2', '\0' };
+const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '0', '3', '\0' }; // inline caches
static constexpr uint16_t kMaxDexFileKeyLength = PATH_MAX;
@@ -46,6 +46,25 @@
// using the same test profile.
static constexpr bool kDebugIgnoreChecksum = false;
+static constexpr uint8_t kMegamorphicEncoding = 7;
+
+static_assert(sizeof(InlineCache::kIndividualCacheSize) == sizeof(uint8_t),
+ "InlineCache::kIndividualCacheSize does not have the expect type size");
+static_assert(InlineCache::kIndividualCacheSize < kMegamorphicEncoding,
+ "InlineCache::kIndividualCacheSize is larger than expected");
+
+void ProfileCompilationInfo::DexPcData::AddClass(uint16_t dex_profile_idx,
+ const dex::TypeIndex& type_idx) {
+ if (is_megamorphic) {
+ return;
+ }
+ classes.emplace(dex_profile_idx, type_idx);
+ if (classes.size() >= InlineCache::kIndividualCacheSize) {
+ is_megamorphic = true;
+ classes.clear();
+ }
+}
+
// Transform the actual dex location into relative paths.
// Note: this is OK because we don't store profiles of different apps into the same file.
// Apps with split apks don't cause trouble because each split has a different name and will not
@@ -62,12 +81,10 @@
}
bool ProfileCompilationInfo::AddMethodsAndClasses(
- const std::vector<MethodReference>& methods,
+ const std::vector<ProfileMethodInfo>& methods,
const std::set<DexCacheResolvedClasses>& resolved_classes) {
- for (const MethodReference& method : methods) {
- if (!AddMethodIndex(GetProfileDexFileKey(method.dex_file->GetLocation()),
- method.dex_file->GetLocationChecksum(),
- method.dex_method_index)) {
+ for (const ProfileMethodInfo& method : methods) {
+ if (!AddMethod(method)) {
return false;
}
}
@@ -170,29 +187,40 @@
}
static constexpr size_t kLineHeaderSize =
- 3 * sizeof(uint16_t) + // method_set.size + class_set.size + dex_location.size
- sizeof(uint32_t); // checksum
+ 2 * sizeof(uint16_t) + // class_set.size + dex_location.size
+ 2 * sizeof(uint32_t); // method_map.size + checksum
/**
* Serialization format:
- * magic,version,number_of_lines
- * dex_location1,number_of_methods1,number_of_classes1,dex_location_checksum1, \
- * method_id11,method_id12...,class_id1,class_id2...
- * dex_location2,number_of_methods2,number_of_classes2,dex_location_checksum2, \
- * method_id21,method_id22...,,class_id1,class_id2...
+ * magic,version,number_of_dex_files
+ * dex_location1,number_of_classes1,methods_region_size,dex_location_checksum1, \
+ * method_encoding_11,method_encoding_12...,class_id1,class_id2...
+ * dex_location2,number_of_classes2,methods_region_size,dex_location_checksum2, \
+ * method_encoding_21,method_encoding_22...,,class_id1,class_id2...
* .....
+ * The method_encoding is:
+ * method_id,number_of_inline_caches,inline_cache1,inline_cache2...
+ * The inline_cache is:
+ * dex_pc,[M|dex_map_size], dex_profile_index,class_id1,class_id2...,dex_profile_index2,...
+ * dex_map_size is the number of dex_indeces that follows.
+ * Classes are grouped per their dex files and the line
+ * `dex_profile_index,class_id1,class_id2...,dex_profile_index2,...` encodes the
+ * mapping from `dex_profile_index` to the set of classes `class_id1,class_id2...`
+ * M stands for megamorphic and it's encoded as the byte kMegamorphicEncoding.
+ * When present, there will be no class ids following.
**/
bool ProfileCompilationInfo::Save(int fd) {
ScopedTrace trace(__PRETTY_FUNCTION__);
DCHECK_GE(fd, 0);
- // Cache at most 5KB before writing.
- static constexpr size_t kMaxSizeToKeepBeforeWriting = 5 * KB;
+ // Cache at most 50KB before writing.
+ static constexpr size_t kMaxSizeToKeepBeforeWriting = 50 * KB;
// Use a vector wrapper to avoid keeping track of offsets when we add elements.
std::vector<uint8_t> buffer;
WriteBuffer(fd, kProfileMagic, sizeof(kProfileMagic));
WriteBuffer(fd, kProfileVersion, sizeof(kProfileVersion));
- AddUintToBuffer(&buffer, static_cast<uint16_t>(info_.size()));
+ DCHECK_LE(info_.size(), std::numeric_limits<uint8_t>::max());
+ AddUintToBuffer(&buffer, static_cast<uint8_t>(info_.size()));
for (const auto& it : info_) {
if (buffer.size() > kMaxSizeToKeepBeforeWriting) {
@@ -203,9 +231,9 @@
}
const std::string& dex_location = it.first;
const DexFileData& dex_data = it.second;
- if (dex_data.method_set.empty() && dex_data.class_set.empty()) {
- continue;
- }
+
+ // Note that we allow dex files without any methods or classes, so that
+ // inline caches can refer valid dex files.
if (dex_location.size() >= kMaxDexFileKeyLength) {
LOG(WARNING) << "DexFileKey exceeds allocated limit";
@@ -214,42 +242,128 @@
// Make sure that the buffer has enough capacity to avoid repeated resizings
// while we add data.
+ uint32_t methods_region_size = GetMethodsRegionSize(dex_data);
size_t required_capacity = buffer.size() +
kLineHeaderSize +
dex_location.size() +
- sizeof(uint16_t) * (dex_data.class_set.size() + dex_data.method_set.size());
+ sizeof(uint16_t) * dex_data.class_set.size() +
+ methods_region_size;
buffer.reserve(required_capacity);
-
DCHECK_LE(dex_location.size(), std::numeric_limits<uint16_t>::max());
- DCHECK_LE(dex_data.method_set.size(), std::numeric_limits<uint16_t>::max());
DCHECK_LE(dex_data.class_set.size(), std::numeric_limits<uint16_t>::max());
AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_location.size()));
- AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_data.method_set.size()));
AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_data.class_set.size()));
+ AddUintToBuffer(&buffer, methods_region_size); // uint32_t
AddUintToBuffer(&buffer, dex_data.checksum); // uint32_t
AddStringToBuffer(&buffer, dex_location);
- for (auto method_it : dex_data.method_set) {
- AddUintToBuffer(&buffer, method_it);
+ for (const auto& method_it : dex_data.method_map) {
+ AddUintToBuffer(&buffer, method_it.first);
+ AddInlineCacheToBuffer(&buffer, method_it.second);
}
- for (auto class_id : dex_data.class_set) {
+ for (const auto& class_id : dex_data.class_set) {
AddUintToBuffer(&buffer, class_id.index_);
}
- DCHECK_EQ(required_capacity, buffer.size())
+
+ DCHECK_LE(required_capacity, buffer.size())
<< "Failed to add the expected number of bytes in the buffer";
}
return WriteBuffer(fd, buffer.data(), buffer.size());
}
+void ProfileCompilationInfo::AddInlineCacheToBuffer(std::vector<uint8_t>* buffer,
+ const InlineCacheMap& inline_cache_map) {
+ // Add inline cache map size.
+ AddUintToBuffer(buffer, static_cast<uint16_t>(inline_cache_map.size()));
+ if (inline_cache_map.size() == 0) {
+ return;
+ }
+ for (const auto& inline_cache_it : inline_cache_map) {
+ uint16_t dex_pc = inline_cache_it.first;
+ const DexPcData dex_pc_data = inline_cache_it.second;
+ const ClassSet& classes = dex_pc_data.classes;
+
+ // Add the dex pc.
+ AddUintToBuffer(buffer, dex_pc);
+
+ if (dex_pc_data.is_megamorphic) {
+ // Add the megamorphic encoding if needed and continue.
+ // If megamorphic, we don't add the rest of the classes.
+ AddUintToBuffer(buffer, kMegamorphicEncoding);
+ continue;
+ }
+
+ DCHECK_LT(classes.size(), InlineCache::kIndividualCacheSize);
+ DCHECK_NE(classes.size(), 0u) << "InlineCache contains a dex_pc with 0 classes";
+
+ SafeMap<uint8_t, std::vector<dex::TypeIndex>> dex_to_classes_map;
+ // Group the classes by dex. We expect that most of the classes will come from
+ // the same dex, so this will be more efficient than encoding the dex index
+ // for each class reference.
+ GroupClassesByDex(classes, &dex_to_classes_map);
+ // Add the dex map size.
+ AddUintToBuffer(buffer, static_cast<uint8_t>(dex_to_classes_map.size()));
+ for (const auto& dex_it : dex_to_classes_map) {
+ uint8_t dex_profile_index = dex_it.first;
+ const std::vector<dex::TypeIndex>& dex_classes = dex_it.second;
+ // Add the dex profile index.
+ AddUintToBuffer(buffer, dex_profile_index);
+ // Add the the number of classes for each dex profile index.
+ AddUintToBuffer(buffer, static_cast<uint8_t>(dex_classes.size()));
+ for (size_t i = 0; i < dex_classes.size(); i++) {
+ // Add the type index of the classes.
+ AddUintToBuffer(buffer, dex_classes[i].index_);
+ }
+ }
+ }
+}
+
+uint32_t ProfileCompilationInfo::GetMethodsRegionSize(const DexFileData& dex_data) {
+ // ((uint16_t)method index + (uint16_t)inline cache size) * number of methods
+ uint32_t size = 2 * sizeof(uint16_t) * dex_data.method_map.size();
+ for (const auto& method_it : dex_data.method_map) {
+ const InlineCacheMap& inline_cache = method_it.second;
+ size += sizeof(uint16_t) * inline_cache.size(); // dex_pc
+ for (const auto& inline_cache_it : inline_cache) {
+ const ClassSet& classes = inline_cache_it.second.classes;
+ SafeMap<uint8_t, std::vector<dex::TypeIndex>> dex_to_classes_map;
+ GroupClassesByDex(classes, &dex_to_classes_map);
+ size += sizeof(uint8_t); // dex_to_classes_map size
+ for (const auto& dex_it : dex_to_classes_map) {
+ size += sizeof(uint8_t); // dex profile index
+ size += sizeof(uint8_t); // number of classes
+ const std::vector<dex::TypeIndex>& dex_classes = dex_it.second;
+ size += sizeof(uint16_t) * dex_classes.size(); // the actual classes
+ }
+ }
+ }
+ return size;
+}
+
+void ProfileCompilationInfo::GroupClassesByDex(
+ const ClassSet& classes,
+ /*out*/SafeMap<uint8_t, std::vector<dex::TypeIndex>>* dex_to_classes_map) {
+ for (const auto& classes_it : classes) {
+ auto dex_it = dex_to_classes_map->FindOrAdd(classes_it.dex_profile_index);
+ dex_it->second.push_back(classes_it.type_index);
+ }
+}
+
ProfileCompilationInfo::DexFileData* ProfileCompilationInfo::GetOrAddDexFileData(
const std::string& dex_location,
uint32_t checksum) {
- auto info_it = info_.find(dex_location);
- if (info_it == info_.end()) {
- info_it = info_.Put(dex_location, DexFileData(checksum));
+ auto info_it = info_.FindOrAdd(dex_location, DexFileData(checksum, info_.size()));
+ if (info_.size() > std::numeric_limits<uint8_t>::max()) {
+ // Allow only 255 dex files to be profiled. This allows us to save bytes
+ // when encoding. The number is well above what we expect for normal applications.
+ if (kIsDebugBuild) {
+ LOG(WARNING) << "Exceeded the maximum number of dex files (255). Something went wrong";
+ }
+ info_.erase(dex_location);
+ return nullptr;
}
if (info_it->second.checksum != checksum) {
LOG(WARNING) << "Checksum mismatch for dex " << dex_location;
@@ -270,13 +384,65 @@
}
bool ProfileCompilationInfo::AddMethodIndex(const std::string& dex_location,
- uint32_t checksum,
- uint16_t method_idx) {
- DexFileData* const data = GetOrAddDexFileData(dex_location, checksum);
- if (data == nullptr) {
+ uint32_t dex_checksum,
+ uint16_t method_index) {
+ return AddMethod(dex_location, dex_checksum, method_index, OfflineProfileMethodInfo());
+}
+
+bool ProfileCompilationInfo::AddMethod(const std::string& dex_location,
+ uint32_t dex_checksum,
+ uint16_t method_index,
+ const OfflineProfileMethodInfo& pmi) {
+ DexFileData* const data = GetOrAddDexFileData(
+ GetProfileDexFileKey(dex_location),
+ dex_checksum);
+ if (data == nullptr) { // checksum mismatch
return false;
}
- data->method_set.insert(method_idx);
+ auto inline_cache_it = data->method_map.FindOrAdd(method_index);
+ for (const auto& pmi_inline_cache_it : pmi.inline_caches) {
+ uint16_t pmi_ic_dex_pc = pmi_inline_cache_it.first;
+ const DexPcData& pmi_ic_dex_pc_data = pmi_inline_cache_it.second;
+ auto dex_pc_data_it = inline_cache_it->second.FindOrAdd(pmi_ic_dex_pc);
+ if (pmi_ic_dex_pc_data.is_megamorphic) {
+ dex_pc_data_it->second.SetMegamorphic();
+ continue;
+ }
+ for (const ClassReference& class_ref : pmi_ic_dex_pc_data.classes) {
+ const DexReference& dex_ref = pmi.dex_references[class_ref.dex_profile_index];
+ DexFileData* class_dex_data = GetOrAddDexFileData(
+ GetProfileDexFileKey(dex_ref.dex_location),
+ dex_ref.dex_checksum);
+ if (class_dex_data == nullptr) { // checksum mismatch
+ return false;
+ }
+ dex_pc_data_it->second.AddClass(class_dex_data->profile_index, class_ref.type_index);
+ }
+ }
+ return true;
+}
+
+bool ProfileCompilationInfo::AddMethod(const ProfileMethodInfo& pmi) {
+ DexFileData* const data = GetOrAddDexFileData(
+ GetProfileDexFileKey(pmi.dex_file->GetLocation()),
+ pmi.dex_file->GetLocationChecksum());
+ if (data == nullptr) { // checksum mismatch
+ return false;
+ }
+ auto inline_cache_it = data->method_map.FindOrAdd(pmi.dex_method_index);
+
+ for (const ProfileMethodInfo::ProfileInlineCache& cache : pmi.inline_caches) {
+ for (const ProfileMethodInfo::ProfileClassReference& class_ref : cache.classes) {
+ DexFileData* class_dex_data = GetOrAddDexFileData(
+ GetProfileDexFileKey(class_ref.dex_file->GetLocation()),
+ class_ref.dex_file->GetLocationChecksum());
+ if (class_dex_data == nullptr) { // checksum mismatch
+ return false;
+ }
+ auto dex_pc_data_it = inline_cache_it->second.FindOrAdd(cache.dex_pc);
+ dex_pc_data_it->second.AddClass(class_dex_data->profile_index, class_ref.type_index);
+ }
+ }
return true;
}
@@ -291,21 +457,79 @@
return true;
}
-bool ProfileCompilationInfo::ProcessLine(SafeBuffer& line_buffer,
- uint16_t method_set_size,
- uint16_t class_set_size,
- uint32_t checksum,
- const std::string& dex_location) {
- for (uint16_t i = 0; i < method_set_size; i++) {
- uint16_t method_idx = line_buffer.ReadUintAndAdvance<uint16_t>();
- if (!AddMethodIndex(dex_location, checksum, method_idx)) {
+#define READ_UINT(type, buffer, dest, error) \
+ do { \
+ if (!buffer.ReadUintAndAdvance<type>(&dest)) { \
+ *error = "Could not read "#dest; \
+ return false; \
+ } \
+ } \
+ while (false)
+
+bool ProfileCompilationInfo::ReadInlineCache(SafeBuffer& buffer,
+ uint8_t number_of_dex_files,
+ /*out*/ InlineCacheMap* inline_cache,
+ /*out*/ std::string* error) {
+ uint16_t inline_cache_size;
+ READ_UINT(uint16_t, buffer, inline_cache_size, error);
+ for (; inline_cache_size > 0; inline_cache_size--) {
+ uint16_t dex_pc;
+ uint8_t dex_to_classes_map_size;
+ READ_UINT(uint16_t, buffer, dex_pc, error);
+ READ_UINT(uint8_t, buffer, dex_to_classes_map_size, error);
+ auto dex_pc_data_it = inline_cache->FindOrAdd(dex_pc);
+ if (dex_to_classes_map_size == kMegamorphicEncoding) {
+ dex_pc_data_it->second.SetMegamorphic();
+ continue;
+ }
+ for (; dex_to_classes_map_size > 0; dex_to_classes_map_size--) {
+ uint8_t dex_profile_index;
+ uint8_t dex_classes_size;
+ READ_UINT(uint8_t, buffer, dex_profile_index, error);
+ READ_UINT(uint8_t, buffer, dex_classes_size, error);
+ if (dex_profile_index >= number_of_dex_files) {
+ *error = "dex_profile_index out of bounds ";
+ *error += std::to_string(dex_profile_index) + " " + std::to_string(number_of_dex_files);
+ return false;
+ }
+ for (; dex_classes_size > 0; dex_classes_size--) {
+ uint16_t type_index;
+ READ_UINT(uint16_t, buffer, type_index, error);
+ dex_pc_data_it->second.AddClass(dex_profile_index, dex::TypeIndex(type_index));
+ }
+ }
+ }
+ return true;
+}
+
+bool ProfileCompilationInfo::ReadMethods(SafeBuffer& buffer,
+ uint8_t number_of_dex_files,
+ const ProfileLineHeader& line_header,
+ /*out*/std::string* error) {
+ while (buffer.HasMoreData()) {
+ DexFileData* const data = GetOrAddDexFileData(line_header.dex_location, line_header.checksum);
+ uint16_t method_index;
+ READ_UINT(uint16_t, buffer, method_index, error);
+
+ auto it = data->method_map.FindOrAdd(method_index);
+ if (!ReadInlineCache(buffer, number_of_dex_files, &(it->second), error)) {
return false;
}
}
- for (uint16_t i = 0; i < class_set_size; i++) {
- uint16_t type_idx = line_buffer.ReadUintAndAdvance<uint16_t>();
- if (!AddClassIndex(dex_location, checksum, dex::TypeIndex(type_idx))) {
+ return true;
+}
+
+bool ProfileCompilationInfo::ReadClasses(SafeBuffer& buffer,
+ uint16_t classes_to_read,
+ const ProfileLineHeader& line_header,
+ /*out*/std::string* error) {
+ for (uint16_t i = 0; i < classes_to_read; i++) {
+ uint16_t type_index;
+ READ_UINT(uint16_t, buffer, type_index, error);
+ if (!AddClassIndex(line_header.dex_location,
+ line_header.checksum,
+ dex::TypeIndex(type_index))) {
return false;
}
}
@@ -324,15 +548,17 @@
// Reads an uint value previously written with AddUintToBuffer.
template <typename T>
-T ProfileCompilationInfo::SafeBuffer::ReadUintAndAdvance() {
+bool ProfileCompilationInfo::SafeBuffer::ReadUintAndAdvance(/*out*/T* value) {
static_assert(std::is_unsigned<T>::value, "Type is not unsigned");
- CHECK_LE(ptr_current_ + sizeof(T), ptr_end_);
- T value = 0;
+ if (ptr_current_ + sizeof(T) > ptr_end_) {
+ return false;
+ }
+ *value = 0;
for (size_t i = 0; i < sizeof(T); i++) {
- value += ptr_current_[i] << (i * kBitsPerByte);
+ *value += ptr_current_[i] << (i * kBitsPerByte);
}
ptr_current_ += sizeof(T);
- return value;
+ return true;
}
bool ProfileCompilationInfo::SafeBuffer::CompareAndAdvance(const uint8_t* data, size_t data_size) {
@@ -346,6 +572,10 @@
return false;
}
+bool ProfileCompilationInfo::SafeBuffer::HasMoreData() {
+ return ptr_current_ < ptr_end_;
+}
+
ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::SafeBuffer::FillFromFd(
int fd,
const std::string& source,
@@ -369,13 +599,13 @@
ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileHeader(
int fd,
- /*out*/uint16_t* number_of_lines,
+ /*out*/uint8_t* number_of_dex_files,
/*out*/std::string* error) {
// Read magic and version
const size_t kMagicVersionSize =
sizeof(kProfileMagic) +
sizeof(kProfileVersion) +
- sizeof(uint16_t); // number of lines
+ sizeof(uint8_t); // number of dex files
SafeBuffer safe_buffer(kMagicVersionSize);
@@ -392,24 +622,38 @@
*error = "Profile version mismatch";
return kProfileLoadVersionMismatch;
}
- *number_of_lines = safe_buffer.ReadUintAndAdvance<uint16_t>();
+ if (!safe_buffer.ReadUintAndAdvance<uint8_t>(number_of_dex_files)) {
+ *error = "Cannot read the number of dex files";
+ return kProfileLoadBadData;
+ }
return kProfileLoadSuccess;
}
+bool ProfileCompilationInfo::ReadProfileLineHeaderElements(SafeBuffer& buffer,
+ /*out*/uint16_t* dex_location_size,
+ /*out*/ProfileLineHeader* line_header,
+ /*out*/std::string* error) {
+ READ_UINT(uint16_t, buffer, *dex_location_size, error);
+ READ_UINT(uint16_t, buffer, line_header->class_set_size, error);
+ READ_UINT(uint32_t, buffer, line_header->method_region_size_bytes, error);
+ READ_UINT(uint32_t, buffer, line_header->checksum, error);
+ return true;
+}
+
ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLineHeader(
int fd,
/*out*/ProfileLineHeader* line_header,
/*out*/std::string* error) {
SafeBuffer header_buffer(kLineHeaderSize);
- ProfileLoadSatus status = header_buffer.FillFromFd(fd, "ReadProfileHeader", error);
+ ProfileLoadSatus status = header_buffer.FillFromFd(fd, "ReadProfileLineHeader", error);
if (status != kProfileLoadSuccess) {
return status;
}
- uint16_t dex_location_size = header_buffer.ReadUintAndAdvance<uint16_t>();
- line_header->method_set_size = header_buffer.ReadUintAndAdvance<uint16_t>();
- line_header->class_set_size = header_buffer.ReadUintAndAdvance<uint16_t>();
- line_header->checksum = header_buffer.ReadUintAndAdvance<uint32_t>();
+ uint16_t dex_location_size;
+ if (!ReadProfileLineHeaderElements(header_buffer, &dex_location_size, line_header, error)) {
+ return kProfileLoadBadData;
+ }
if (dex_location_size == 0 || dex_location_size > kMaxDexFileKeyLength) {
*error = "DexFileKey has an invalid size: " +
@@ -429,37 +673,38 @@
ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLine(
int fd,
+ uint8_t number_of_dex_files,
const ProfileLineHeader& line_header,
/*out*/std::string* error) {
- // Make sure that we don't try to read everything in memory (in case the profile if full).
- // Split readings in chunks of at most 10kb.
- static constexpr uint16_t kMaxNumberOfEntriesToRead = 5120;
- uint16_t methods_left_to_read = line_header.method_set_size;
- uint16_t classes_left_to_read = line_header.class_set_size;
+ if (GetOrAddDexFileData(line_header.dex_location, line_header.checksum) == nullptr) {
+ *error = "Error when reading profile file line header: checksum mismatch for "
+ + line_header.dex_location;
+ return kProfileLoadBadData;
+ }
- while ((methods_left_to_read > 0) || (classes_left_to_read > 0)) {
- uint16_t methods_to_read = std::min(kMaxNumberOfEntriesToRead, methods_left_to_read);
- uint16_t max_classes_to_read = kMaxNumberOfEntriesToRead - methods_to_read;
- uint16_t classes_to_read = std::min(max_classes_to_read, classes_left_to_read);
-
- size_t line_size = sizeof(uint16_t) * (methods_to_read + classes_to_read);
- SafeBuffer line_buffer(line_size);
-
- ProfileLoadSatus status = line_buffer.FillFromFd(fd, "ReadProfileLine", error);
+ {
+ SafeBuffer buffer(line_header.method_region_size_bytes);
+ ProfileLoadSatus status = buffer.FillFromFd(fd, "ReadProfileLineMethods", error);
if (status != kProfileLoadSuccess) {
return status;
}
- if (!ProcessLine(line_buffer,
- methods_to_read,
- classes_to_read,
- line_header.checksum,
- line_header.dex_location)) {
- *error = "Error when reading profile file line";
+
+ if (!ReadMethods(buffer, number_of_dex_files, line_header, error)) {
return kProfileLoadBadData;
}
- methods_left_to_read -= methods_to_read;
- classes_left_to_read -= classes_to_read;
}
+
+ {
+ SafeBuffer buffer(sizeof(uint16_t) * line_header.class_set_size);
+ ProfileLoadSatus status = buffer.FillFromFd(fd, "ReadProfileLineClasses", error);
+ if (status != kProfileLoadSuccess) {
+ return status;
+ }
+ if (!ReadClasses(buffer, line_header.class_set_size, line_header, error)) {
+ return kProfileLoadBadData;
+ }
+ }
+
return kProfileLoadSuccess;
}
@@ -470,7 +715,7 @@
if (status == kProfileLoadSuccess) {
return true;
} else {
- PLOG(WARNING) << "Error when reading profile " << error;
+ LOG(WARNING) << "Error when reading profile: " << error;
return false;
}
}
@@ -490,15 +735,16 @@
if (stat_buffer.st_size == 0) {
return kProfileLoadSuccess;
}
- // Read profile header: magic + version + number_of_lines.
- uint16_t number_of_lines;
- ProfileLoadSatus status = ReadProfileHeader(fd, &number_of_lines, error);
+ // Read profile header: magic + version + number_of_dex_files.
+ uint8_t number_of_dex_files;
+ ProfileLoadSatus status = ReadProfileHeader(fd, &number_of_dex_files, error);
if (status != kProfileLoadSuccess) {
return status;
}
- while (number_of_lines > 0) {
+ for (uint8_t k = 0; k < number_of_dex_files; k++) {
ProfileLineHeader line_header;
+
// First, read the line header to get the amount of data we need to read.
status = ReadProfileLineHeader(fd, &line_header, error);
if (status != kProfileLoadSuccess) {
@@ -506,11 +752,10 @@
}
// Now read the actual profile line.
- status = ReadProfileLine(fd, line_header, error);
+ status = ReadProfileLine(fd, number_of_dex_files, line_header, error);
if (status != kProfileLoadSuccess) {
return status;
}
- number_of_lines--;
}
// Check that we read everything and that profiles don't contain junk data.
@@ -538,37 +783,116 @@
}
}
// All checksums match. Import the data.
+
+ // The other profile might have a different indexing of dex files.
+ // That is because each dex files gets a 'dex_profile_index' on a first come first served basis.
+ // That means that the order in with the methods are added to the profile matters for the
+ // actual indices.
+ // The reason we cannot rely on the actual multidex index is that a single profile may store
+ // data from multiple splits. This means that a profile may contain a classes2.dex from split-A
+ // and one from split-B.
+
+ // First, build a mapping from other_dex_profile_index to this_dex_profile_index.
+ // This will make sure that the ClassReferences will point to the correct dex file.
+ SafeMap<uint8_t, uint8_t> dex_profile_index_remap;
+ for (const auto& other_it : other.info_) {
+ const std::string& other_dex_location = other_it.first;
+ const DexFileData& other_dex_data = other_it.second;
+ auto info_it = info_.FindOrAdd(other_dex_location, DexFileData(other_dex_data.checksum, 0));
+ const DexFileData& dex_data = info_it->second;
+ dex_profile_index_remap.Put(other_dex_data.profile_index, dex_data.profile_index);
+ }
+
+ // Merge the actual profile data.
for (const auto& other_it : other.info_) {
const std::string& other_dex_location = other_it.first;
const DexFileData& other_dex_data = other_it.second;
auto info_it = info_.find(other_dex_location);
- if (info_it == info_.end()) {
- info_it = info_.Put(other_dex_location, DexFileData(other_dex_data.checksum));
- }
- info_it->second.method_set.insert(other_dex_data.method_set.begin(),
- other_dex_data.method_set.end());
+ DCHECK(info_it != info_.end());
+
+ // Merge the classes.
info_it->second.class_set.insert(other_dex_data.class_set.begin(),
other_dex_data.class_set.end());
+
+ // Merge the methods and the inline caches.
+ for (const auto& other_method_it : other_dex_data.method_map) {
+ uint16_t other_method_index = other_method_it.first;
+ auto method_it = info_it->second.method_map.FindOrAdd(other_method_index);
+ const auto& other_inline_cache = other_method_it.second;
+ for (const auto& other_ic_it : other_inline_cache) {
+ uint16_t other_dex_pc = other_ic_it.first;
+ const ClassSet& other_class_set = other_ic_it.second.classes;
+ auto class_set = method_it->second.FindOrAdd(other_dex_pc);
+ if (other_ic_it.second.is_megamorphic) {
+ class_set->second.SetMegamorphic();
+ } else {
+ for (const auto& class_it : other_class_set) {
+ class_set->second.AddClass(dex_profile_index_remap.Get(
+ class_it.dex_profile_index), class_it.type_index);
+ }
+ }
+ }
+ }
}
return true;
}
+static bool ChecksumMatch(uint32_t dex_file_checksum, uint32_t checksum) {
+ return kDebugIgnoreChecksum || dex_file_checksum == checksum;
+}
+
static bool ChecksumMatch(const DexFile& dex_file, uint32_t checksum) {
- return kDebugIgnoreChecksum || dex_file.GetLocationChecksum() == checksum;
+ return ChecksumMatch(dex_file.GetLocationChecksum(), checksum);
}
bool ProfileCompilationInfo::ContainsMethod(const MethodReference& method_ref) const {
- auto info_it = info_.find(GetProfileDexFileKey(method_ref.dex_file->GetLocation()));
- if (info_it != info_.end()) {
- if (!ChecksumMatch(*method_ref.dex_file, info_it->second.checksum)) {
- return false;
- }
- const std::set<uint16_t>& methods = info_it->second.method_set;
- return methods.find(method_ref.dex_method_index) != methods.end();
- }
- return false;
+ return FindMethod(method_ref.dex_file->GetLocation(),
+ method_ref.dex_file->GetLocationChecksum(),
+ method_ref.dex_method_index) != nullptr;
}
+const ProfileCompilationInfo::InlineCacheMap*
+ProfileCompilationInfo::FindMethod(const std::string& dex_location,
+ uint32_t dex_checksum,
+ uint16_t dex_method_index) const {
+ auto info_it = info_.find(GetProfileDexFileKey(dex_location));
+ if (info_it != info_.end()) {
+ if (!ChecksumMatch(dex_checksum, info_it->second.checksum)) {
+ return nullptr;
+ }
+ const MethodMap& methods = info_it->second.method_map;
+ const auto method_it = methods.find(dex_method_index);
+ return method_it == methods.end() ? nullptr : &(method_it->second);
+ }
+ return nullptr;
+}
+
+void ProfileCompilationInfo::DexFileToProfileIndex(
+ /*out*/std::vector<DexReference>* dex_references) const {
+ dex_references->resize(info_.size());
+ for (const auto& info_it : info_) {
+ DexReference& dex_ref = (*dex_references)[info_it.second.profile_index];
+ dex_ref.dex_location = info_it.first;
+ dex_ref.dex_checksum = info_it.second.checksum;
+ }
+}
+
+bool ProfileCompilationInfo::GetMethod(const std::string& dex_location,
+ uint32_t dex_checksum,
+ uint16_t dex_method_index,
+ /*out*/OfflineProfileMethodInfo* pmi) const {
+ const InlineCacheMap* inline_caches = FindMethod(dex_location, dex_checksum, dex_method_index);
+ if (inline_caches == nullptr) {
+ return false;
+ }
+
+ DexFileToProfileIndex(&pmi->dex_references);
+ // TODO(calin): maybe expose a direct pointer to avoid copying
+ pmi->inline_caches = *inline_caches;
+ return true;
+}
+
+
bool ProfileCompilationInfo::ContainsClass(const DexFile& dex_file, dex::TypeIndex type_idx) const {
auto info_it = info_.find(GetProfileDexFileKey(dex_file.GetLocation()));
if (info_it != info_.end()) {
@@ -584,7 +908,7 @@
uint32_t ProfileCompilationInfo::GetNumberOfMethods() const {
uint32_t total = 0;
for (const auto& it : info_) {
- total += it.second.method_set.size();
+ total += it.second.method_map.size();
}
return total;
}
@@ -645,19 +969,34 @@
}
}
os << "\n\tmethods: ";
- for (const auto method_it : dex_data.method_set) {
+ for (const auto method_it : dex_data.method_map) {
if (dex_file != nullptr) {
- os << "\n\t\t" << dex_file->PrettyMethod(method_it, true);
+ os << "\n\t\t" << dex_file->PrettyMethod(method_it.first, true);
} else {
- os << method_it << ",";
+ os << method_it.first;
}
+
+ os << "[";
+ for (const auto& inline_cache_it : method_it.second) {
+ os << "{" << std::hex << inline_cache_it.first << std::dec << ":";
+ if (inline_cache_it.second.is_megamorphic) {
+ os << "M";
+ } else {
+ for (const ClassReference& class_ref : inline_cache_it.second.classes) {
+ os << "(" << static_cast<uint32_t>(class_ref.dex_profile_index)
+ << "," << class_ref.type_index.index_ << ")";
+ }
+ }
+ os << "}";
+ }
+ os << "], ";
}
os << "\n\tclasses: ";
for (const auto class_it : dex_data.class_set) {
if (dex_file != nullptr) {
os << "\n\t\t" << dex_file->PrettyType(class_it);
} else {
- os << class_it << ",";
+ os << class_it.index_ << ",";
}
}
}
@@ -762,4 +1101,44 @@
return info.Save(fd);
}
+bool ProfileCompilationInfo::OfflineProfileMethodInfo::operator==(
+ const OfflineProfileMethodInfo& other) const {
+ if (inline_caches.size() != other.inline_caches.size()) {
+ return false;
+ }
+
+ // We can't use a simple equality test because we need to match the dex files
+ // of the inline caches which might have different profile indices.
+ for (const auto& inline_cache_it : inline_caches) {
+ uint16_t dex_pc = inline_cache_it.first;
+ const DexPcData dex_pc_data = inline_cache_it.second;
+ const auto other_it = other.inline_caches.find(dex_pc);
+ if (other_it == other.inline_caches.end()) {
+ return false;
+ }
+ const DexPcData& other_dex_pc_data = other_it->second;
+ if (dex_pc_data.is_megamorphic != other_dex_pc_data.is_megamorphic) {
+ return false;
+ }
+ for (const ClassReference& class_ref : dex_pc_data.classes) {
+ bool found = false;
+ for (const ClassReference& other_class_ref : other_dex_pc_data.classes) {
+ CHECK_LE(class_ref.dex_profile_index, dex_references.size());
+ CHECK_LE(other_class_ref.dex_profile_index, other.dex_references.size());
+ const DexReference& dex_ref = dex_references[class_ref.dex_profile_index];
+ const DexReference& other_dex_ref = other.dex_references[other_class_ref.dex_profile_index];
+ if (class_ref.type_index == other_class_ref.type_index &&
+ dex_ref == other_dex_ref) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
} // namespace art
diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h
index 758b46d..4bfbfcd 100644
--- a/runtime/jit/profile_compilation_info.h
+++ b/runtime/jit/profile_compilation_info.h
@@ -31,6 +31,40 @@
namespace art {
/**
+ * Convenient class to pass around profile information (including inline caches)
+ * without the need to hold GC-able objects.
+ */
+struct ProfileMethodInfo {
+ struct ProfileClassReference {
+ ProfileClassReference(const DexFile* dex, const dex::TypeIndex& index)
+ : dex_file(dex), type_index(index) {}
+
+ const DexFile* dex_file;
+ const dex::TypeIndex type_index;
+ };
+
+ struct ProfileInlineCache {
+ ProfileInlineCache(uint32_t pc, const std::vector<ProfileClassReference>& profile_classes)
+ : dex_pc(pc), classes(profile_classes) {}
+
+ const uint32_t dex_pc;
+ const std::vector<ProfileClassReference> classes;
+ };
+
+ ProfileMethodInfo(const DexFile* dex, uint32_t method_index)
+ : dex_file(dex), dex_method_index(method_index) {}
+
+ ProfileMethodInfo(const DexFile* dex,
+ uint32_t method_index,
+ const std::vector<ProfileInlineCache>& caches)
+ : dex_file(dex), dex_method_index(method_index), inline_caches(caches) {}
+
+ const DexFile* dex_file;
+ const uint32_t dex_method_index;
+ const std::vector<ProfileInlineCache> inline_caches;
+};
+
+/**
* Profile information in a format suitable to be queried by the compiler and
* performing profile guided compilation.
* It is a serialize-friendly format based on information collected by the
@@ -42,34 +76,130 @@
static const uint8_t kProfileMagic[];
static const uint8_t kProfileVersion[];
+ // Data structures for encoding the offline representation of inline caches.
+ // This is exposed as public in order to make it available to dex2oat compilations
+ // (see compiler/optimizing/inliner.cc).
+
+ // A dex location together with its checksum.
+ struct DexReference {
+ DexReference() {}
+
+ DexReference(const std::string& location, uint32_t checksum)
+ : dex_location(location), dex_checksum(checksum) {}
+
+ bool operator==(const DexReference& other) const {
+ return dex_checksum == other.dex_checksum && dex_location == other.dex_location;
+ }
+
+ std::string dex_location;
+ uint32_t dex_checksum;
+ };
+
+ // Encodes a class reference in the profile.
+ // The owning dex file is encoded as the index (dex_profile_index) it has in the
+ // profile rather than as a full DexRefence(location,checksum).
+ // This avoids excessive string copying when managing the profile data.
+ // The dex_profile_index is an index in either of:
+ // - OfflineProfileMethodInfo#dex_references vector (public use)
+ // - DexFileData#profile_index (internal use).
+ // Note that the dex_profile_index is not necessary the multidex index.
+ // We cannot rely on the actual multidex index because a single profile may store
+ // data from multiple splits. This means that a profile may contain a classes2.dex from split-A
+ // and one from split-B.
+ struct ClassReference {
+ ClassReference(uint8_t dex_profile_idx, const dex::TypeIndex& type_idx) :
+ dex_profile_index(dex_profile_idx), type_index(type_idx) {}
+
+ bool operator==(const ClassReference& other) const {
+ return dex_profile_index == other.dex_profile_index && type_index == other.type_index;
+ }
+ bool operator<(const ClassReference& other) const {
+ return dex_profile_index == other.dex_profile_index
+ ? type_index < other.type_index
+ : dex_profile_index < other.dex_profile_index;
+ }
+
+ uint8_t dex_profile_index; // the index of the owning dex in the profile info
+ dex::TypeIndex type_index; // the type index of the class
+ };
+
+ // The set of classes that can be found at a given dex pc.
+ using ClassSet = std::set<ClassReference>;
+
+ // Encodes the actual inline cache for a given dex pc (whether or not the receiver is
+ // megamorphic and its possible types).
+ // If the receiver is megamorphic the set of classes will be empty.
+ struct DexPcData {
+ DexPcData() : is_megamorphic(false) {}
+ void AddClass(uint16_t dex_profile_idx, const dex::TypeIndex& type_idx);
+ void SetMegamorphic() {
+ is_megamorphic = true;
+ classes.clear();
+ }
+ bool operator==(const DexPcData& other) const {
+ return is_megamorphic == other.is_megamorphic && classes == other.classes;
+ }
+
+ bool is_megamorphic;
+ ClassSet classes;
+ };
+
+ // The inline cache map: DexPc -> DexPcData.
+ using InlineCacheMap = SafeMap<uint16_t, DexPcData>;
+
+ // Encodes the full set of inline caches for a given method.
+ // The dex_references vector is indexed according to the ClassReference::dex_profile_index.
+ // i.e. the dex file of any ClassReference present in the inline caches can be found at
+ // dex_references[ClassReference::dex_profile_index].
+ struct OfflineProfileMethodInfo {
+ bool operator==(const OfflineProfileMethodInfo& other) const;
+
+ std::vector<DexReference> dex_references;
+ InlineCacheMap inline_caches;
+ };
+
+ // Public methods to create, extend or query the profile.
+
// Add the given methods and classes to the current profile object.
- bool AddMethodsAndClasses(const std::vector<MethodReference>& methods,
+ bool AddMethodsAndClasses(const std::vector<ProfileMethodInfo>& methods,
const std::set<DexCacheResolvedClasses>& resolved_classes);
- // Loads profile information from the given file descriptor.
+
+ // Load profile information from the given file descriptor.
bool Load(int fd);
+
// Merge the data from another ProfileCompilationInfo into the current object.
bool MergeWith(const ProfileCompilationInfo& info);
- // Saves the profile data to the given file descriptor.
+
+ // Save the profile data to the given file descriptor.
bool Save(int fd);
- // Loads and merges profile information from the given file into the current
+
+ // Load and merge profile information from the given file into the current
// object and tries to save it back to disk.
// If `force` is true then the save will go through even if the given file
// has bad data or its version does not match. In this cases the profile content
// is ignored.
bool MergeAndSave(const std::string& filename, uint64_t* bytes_written, bool force);
- // Returns the number of methods that were profiled.
+ // Return the number of methods that were profiled.
uint32_t GetNumberOfMethods() const;
- // Returns the number of resolved classes that were profiled.
+
+ // Return the number of resolved classes that were profiled.
uint32_t GetNumberOfResolvedClasses() const;
- // Returns true if the method reference is present in the profiling info.
+ // Return true if the method reference is present in the profiling info.
bool ContainsMethod(const MethodReference& method_ref) const;
- // Returns true if the class's type is present in the profiling info.
+ // Return true if the class's type is present in the profiling info.
bool ContainsClass(const DexFile& dex_file, dex::TypeIndex type_idx) const;
- // Dumps all the loaded profile info into a string and returns it.
+ // Return true if the method is present in the profiling info.
+ // If the method is found, `pmi` is populated with its inline caches.
+ bool GetMethod(const std::string& dex_location,
+ uint32_t dex_checksum,
+ uint16_t dex_method_index,
+ /*out*/OfflineProfileMethodInfo* pmi) const;
+
+ // Dump all the loaded profile info into a string and returns it.
// If dex_files is not null then the method indices will be resolved to their
// names.
// This is intended for testing and debugging.
@@ -80,26 +210,35 @@
void GetClassNames(const std::vector<std::unique_ptr<const DexFile>>* dex_files,
std::set<std::string>* class_names) const;
+
void GetClassNames(const std::vector<const DexFile*>* dex_files,
std::set<std::string>* class_names) const;
+ // Perform an equality test with the `other` profile information.
bool Equals(const ProfileCompilationInfo& other);
- static std::string GetProfileDexFileKey(const std::string& dex_location);
-
- // Returns the class descriptors for all of the classes in the profiles' class sets.
+ // Return the class descriptors for all of the classes in the profiles' class sets.
// Note the dex location is actually the profile key, the caller needs to call back in to the
// profile info stuff to generate a map back to the dex location.
std::set<DexCacheResolvedClasses> GetResolvedClasses() const;
- // Clears the resolved classes from the current object.
+ // Clear the resolved classes from the current object.
void ClearResolvedClasses();
+ // Return the profile key associated with the given dex location.
+ static std::string GetProfileDexFileKey(const std::string& dex_location);
+
+ // Generate a test profile which will contain a percentage of the total maximum
+ // number of methods and classes (method_ratio and class_ratio).
static bool GenerateTestProfile(int fd,
uint16_t number_of_dex_files,
uint16_t method_ratio,
uint16_t class_ratio);
+ // Check that the given profile method info contain the same data.
+ static bool Equals(const ProfileCompilationInfo::OfflineProfileMethodInfo& pmi1,
+ const ProfileCompilationInfo::OfflineProfileMethodInfo& pmi2);
+
private:
enum ProfileLoadSatus {
kProfileLoadIOError,
@@ -108,30 +247,71 @@
kProfileLoadSuccess
};
+ // Maps a method dex index to its inline cache.
+ using MethodMap = SafeMap<uint16_t, InlineCacheMap>;
+
+ // Internal representation of the profile information belonging to a dex file.
struct DexFileData {
- explicit DexFileData(uint32_t location_checksum) : checksum(location_checksum) {}
+ DexFileData(uint32_t location_checksum, uint16_t index)
+ : profile_index(index), checksum(location_checksum) {}
+ // The profile index of this dex file (matches ClassReference#dex_profile_index)
+ uint8_t profile_index;
+ // The dex checksum
uint32_t checksum;
- std::set<uint16_t> method_set;
+ // The methonds' profile information
+ MethodMap method_map;
+ // The classes which have been profiled. Note that these don't necessarily include
+ // all the classes that can be found in the inline caches reference.
std::set<dex::TypeIndex> class_set;
bool operator==(const DexFileData& other) const {
- return checksum == other.checksum && method_set == other.method_set;
+ return checksum == other.checksum && method_map == other.method_map;
}
};
+ // Maps dex file to their profile information.
using DexFileToProfileInfoMap = SafeMap<const std::string, DexFileData>;
+ // Return the profile data for the given dex location or null if the dex location
+ // already exists but has a different checksum
DexFileData* GetOrAddDexFileData(const std::string& dex_location, uint32_t checksum);
+
+ // Add a method index to the profile (without inline caches).
bool AddMethodIndex(const std::string& dex_location, uint32_t checksum, uint16_t method_idx);
+
+ // Add a method to the profile using its online representation (containing runtime structures).
+ bool AddMethod(const ProfileMethodInfo& pmi);
+
+ // Add a method to the profile using its offline representation.
+ // This is mostly used to facilitate testing.
+ bool AddMethod(const std::string& dex_location,
+ uint32_t dex_checksum,
+ uint16_t method_index,
+ const OfflineProfileMethodInfo& pmi);
+
+ // Add a class index to the profile.
bool AddClassIndex(const std::string& dex_location, uint32_t checksum, dex::TypeIndex type_idx);
+
+ // Add all classes from the given dex cache to the the profile.
bool AddResolvedClasses(const DexCacheResolvedClasses& classes);
+ // Search for the given method in the profile.
+ // If found, its inline cache map is returned, otherwise the method returns null.
+ const InlineCacheMap* FindMethod(const std::string& dex_location,
+ uint32_t dex_checksum,
+ uint16_t dex_method_index) const;
+
+ // Encode the known dex_files into a vector. The index of a dex_reference will
+ // be the same as the profile index of the dex file (used to encode the ClassReferences).
+ void DexFileToProfileIndex(/*out*/std::vector<DexReference>* dex_references) const;
+
// Parsing functionality.
+ // The information present in the header of each profile line.
struct ProfileLineHeader {
std::string dex_location;
- uint16_t method_set_size;
uint16_t class_set_size;
+ uint32_t method_region_size_bytes;
uint32_t checksum;
};
@@ -150,12 +330,15 @@
// Reads an uint value (high bits to low bits) and advances the current pointer
// with the number of bits read.
- template <typename T> T ReadUintAndAdvance();
+ template <typename T> bool ReadUintAndAdvance(/*out*/ T* value);
// Compares the given data with the content current pointer. If the contents are
// equal it advances the current pointer by data_size.
bool CompareAndAdvance(const uint8_t* data, size_t data_size);
+ // Returns true if the buffer has more data to read.
+ bool HasMoreData();
+
// Get the underlying raw buffer.
uint8_t* Get() { return storage_.get(); }
@@ -165,24 +348,63 @@
uint8_t* ptr_end_;
};
+ // Entry point for profile loding functionality.
ProfileLoadSatus LoadInternal(int fd, std::string* error);
+ // Read the profile header from the given fd and store the number of profile
+ // lines into number_of_dex_files.
ProfileLoadSatus ReadProfileHeader(int fd,
- /*out*/uint16_t* number_of_lines,
+ /*out*/uint8_t* number_of_dex_files,
/*out*/std::string* error);
+ // Read the header of a profile line from the given fd.
ProfileLoadSatus ReadProfileLineHeader(int fd,
/*out*/ProfileLineHeader* line_header,
/*out*/std::string* error);
+
+ // Read individual elements from the profile line header.
+ bool ReadProfileLineHeaderElements(SafeBuffer& buffer,
+ /*out*/uint16_t* dex_location_size,
+ /*out*/ProfileLineHeader* line_header,
+ /*out*/std::string* error);
+
+ // Read a single profile line from the given fd.
ProfileLoadSatus ReadProfileLine(int fd,
+ uint8_t number_of_dex_files,
const ProfileLineHeader& line_header,
/*out*/std::string* error);
- bool ProcessLine(SafeBuffer& line_buffer,
- uint16_t method_set_size,
- uint16_t class_set_size,
- uint32_t checksum,
- const std::string& dex_location);
+ // Read all the classes from the buffer into the profile `info_` structure.
+ bool ReadClasses(SafeBuffer& buffer,
+ uint16_t classes_to_read,
+ const ProfileLineHeader& line_header,
+ /*out*/std::string* error);
+
+ // Read all the methods from the buffer into the profile `info_` structure.
+ bool ReadMethods(SafeBuffer& buffer,
+ uint8_t number_of_dex_files,
+ const ProfileLineHeader& line_header,
+ /*out*/std::string* error);
+
+ // Read the inline cache encoding from line_bufer into inline_cache.
+ bool ReadInlineCache(SafeBuffer& buffer,
+ uint8_t number_of_dex_files,
+ /*out*/InlineCacheMap* inline_cache,
+ /*out*/std::string* error);
+
+ // Encode the inline cache into the given buffer.
+ void AddInlineCacheToBuffer(std::vector<uint8_t>* buffer,
+ const InlineCacheMap& inline_cache);
+
+ // Return the number of bytes needed to encode the profile information
+ // for the methods in dex_data.
+ uint32_t GetMethodsRegionSize(const DexFileData& dex_data);
+
+ // Group `classes` by their owning dex profile index and put the result in
+ // `dex_to_classes_map`.
+ void GroupClassesByDex(
+ const ClassSet& classes,
+ /*out*/SafeMap<uint8_t, std::vector<dex::TypeIndex>>* dex_to_classes_map);
friend class ProfileCompilationInfoTest;
friend class CompilerDriverProfileTest;
diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc
index 835a5f3..332280a 100644
--- a/runtime/jit/profile_compilation_info_test.cc
+++ b/runtime/jit/profile_compilation_info_test.cc
@@ -57,6 +57,14 @@
return info->AddMethodIndex(dex_location, checksum, method_index);
}
+ bool AddMethod(const std::string& dex_location,
+ uint32_t checksum,
+ uint16_t method_index,
+ const ProfileCompilationInfo::OfflineProfileMethodInfo& pmi,
+ ProfileCompilationInfo* info) {
+ return info->AddMethod(dex_location, checksum, method_index, pmi);
+ }
+
bool AddClass(const std::string& dex_location,
uint32_t checksum,
uint16_t class_index,
@@ -73,17 +81,132 @@
const std::vector<ArtMethod*>& methods,
const std::set<DexCacheResolvedClasses>& resolved_classes) {
ProfileCompilationInfo info;
- std::vector<MethodReference> method_refs;
+ std::vector<ProfileMethodInfo> profile_methods;
ScopedObjectAccess soa(Thread::Current());
for (ArtMethod* method : methods) {
- method_refs.emplace_back(method->GetDexFile(), method->GetDexMethodIndex());
+ profile_methods.emplace_back(method->GetDexFile(), method->GetDexMethodIndex());
}
- if (!info.AddMethodsAndClasses(method_refs, resolved_classes)) {
+ if (!info.AddMethodsAndClasses(profile_methods, resolved_classes)) {
+ return false;
+ }
+ if (info.GetNumberOfMethods() != profile_methods.size()) {
return false;
}
return info.MergeAndSave(filename, nullptr, false);
}
+ // Saves the given art methods to a profile backed by 'filename' and adds
+ // some fake inline caches to it. The added inline caches are returned in
+ // the out map `profile_methods_map`.
+ bool SaveProfilingInfoWithFakeInlineCaches(
+ const std::string& filename,
+ const std::vector<ArtMethod*>& methods,
+ /*out*/ SafeMap<ArtMethod*, ProfileMethodInfo>* profile_methods_map) {
+ ProfileCompilationInfo info;
+ std::vector<ProfileMethodInfo> profile_methods;
+ ScopedObjectAccess soa(Thread::Current());
+ for (ArtMethod* method : methods) {
+ std::vector<ProfileMethodInfo::ProfileInlineCache> caches;
+ // Monomorphic
+ for (uint16_t dex_pc = 0; dex_pc < 1; dex_pc++) {
+ std::vector<ProfileMethodInfo::ProfileClassReference> classes;
+ classes.emplace_back(method->GetDexFile(), dex::TypeIndex(0));
+ caches.emplace_back(dex_pc, classes);
+ }
+ // Polymorphic
+ for (uint16_t dex_pc = 1; dex_pc < 2; dex_pc++) {
+ std::vector<ProfileMethodInfo::ProfileClassReference> classes;
+ for (uint16_t k = 0; k < InlineCache::kIndividualCacheSize / 2; k++) {
+ classes.emplace_back(method->GetDexFile(), dex::TypeIndex(k));
+ }
+ caches.emplace_back(dex_pc, classes);
+ }
+ // Megamorphic
+ for (uint16_t dex_pc = 2; dex_pc < 3; dex_pc++) {
+ std::vector<ProfileMethodInfo::ProfileClassReference> classes;
+ for (uint16_t k = 0; k < 2 * InlineCache::kIndividualCacheSize; k++) {
+ classes.emplace_back(method->GetDexFile(), dex::TypeIndex(k));
+ }
+ caches.emplace_back(dex_pc, classes);
+ }
+ ProfileMethodInfo pmi(method->GetDexFile(), method->GetDexMethodIndex(), caches);
+ profile_methods.push_back(pmi);
+ profile_methods_map->Put(method, pmi);
+ }
+
+ if (!info.AddMethodsAndClasses(profile_methods, std::set<DexCacheResolvedClasses>())) {
+ return false;
+ }
+ if (info.GetNumberOfMethods() != profile_methods.size()) {
+ return false;
+ }
+ return info.MergeAndSave(filename, nullptr, false);
+ }
+
+ ProfileCompilationInfo::OfflineProfileMethodInfo ConvertProfileMethodInfo(
+ const ProfileMethodInfo& pmi) {
+ ProfileCompilationInfo::OfflineProfileMethodInfo offline_pmi;
+ SafeMap<DexFile*, uint8_t> dex_map; // dex files to profile index
+ for (const auto& inline_cache : pmi.inline_caches) {
+ for (const auto& class_ref : inline_cache.classes) {
+ uint8_t dex_profile_index = dex_map.FindOrAdd(const_cast<DexFile*>(class_ref.dex_file),
+ static_cast<uint8_t>(dex_map.size()))->second;
+ offline_pmi.inline_caches
+ .FindOrAdd(inline_cache.dex_pc)->second
+ .AddClass(dex_profile_index, class_ref.type_index);
+ if (dex_profile_index >= offline_pmi.dex_references.size()) {
+ // This is a new dex.
+ const std::string& dex_key = ProfileCompilationInfo::GetProfileDexFileKey(
+ class_ref.dex_file->GetLocation());
+ offline_pmi.dex_references.emplace_back(dex_key,
+ class_ref.dex_file->GetLocationChecksum());
+ }
+ }
+ }
+ return offline_pmi;
+ }
+
+ // Creates an offline profile used for testing inline caches.
+ ProfileCompilationInfo::OfflineProfileMethodInfo GetOfflineProfileMethodInfo() {
+ ProfileCompilationInfo::OfflineProfileMethodInfo pmi;
+
+ pmi.dex_references.emplace_back("dex_location1", /* checksum */ 1);
+ pmi.dex_references.emplace_back("dex_location2", /* checksum */ 2);
+ pmi.dex_references.emplace_back("dex_location3", /* checksum */ 3);
+
+ // Monomorphic
+ for (uint16_t dex_pc = 0; dex_pc < 10; dex_pc++) {
+ ProfileCompilationInfo::DexPcData dex_pc_data;
+ dex_pc_data.AddClass(0, dex::TypeIndex(0));
+ pmi.inline_caches.Put(dex_pc, dex_pc_data);
+ }
+ // Polymorphic
+ for (uint16_t dex_pc = 10; dex_pc < 20; dex_pc++) {
+ ProfileCompilationInfo::DexPcData dex_pc_data;
+ dex_pc_data.AddClass(0, dex::TypeIndex(0));
+ dex_pc_data.AddClass(1, dex::TypeIndex(1));
+ dex_pc_data.AddClass(2, dex::TypeIndex(2));
+
+ pmi.inline_caches.Put(dex_pc, dex_pc_data);
+ }
+ // Megamorphic
+ for (uint16_t dex_pc = 20; dex_pc < 30; dex_pc++) {
+ ProfileCompilationInfo::DexPcData dex_pc_data;
+ dex_pc_data.is_megamorphic = true;
+ pmi.inline_caches.Put(dex_pc, dex_pc_data);
+ }
+
+ return pmi;
+ }
+
+ void MakeMegamorphic(/*out*/ProfileCompilationInfo::OfflineProfileMethodInfo* pmi) {
+ for (auto it : pmi->inline_caches) {
+ for (uint16_t k = 0; k <= 2 * InlineCache::kIndividualCacheSize; k++) {
+ it.second.AddClass(0, dex::TypeIndex(k));
+ }
+ }
+ }
+
// Cannot sizeof the actual arrays so hardcode the values here.
// They should not change anyway.
static constexpr int kProfileMagicSize = 4;
@@ -235,12 +358,12 @@
TEST_F(ProfileCompilationInfoTest, LoadEmpty) {
ScratchFile profile;
- ProfileCompilationInfo empyt_info;
+ ProfileCompilationInfo empty_info;
ProfileCompilationInfo loaded_info;
ASSERT_TRUE(profile.GetFile()->ResetOffset());
ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
- ASSERT_TRUE(loaded_info.Equals(empyt_info));
+ ASSERT_TRUE(loaded_info.Equals(empty_info));
}
TEST_F(ProfileCompilationInfoTest, BadMagic) {
@@ -324,4 +447,243 @@
ASSERT_FALSE(loaded_info.Load(GetFd(profile)));
}
+TEST_F(ProfileCompilationInfoTest, SaveInlineCaches) {
+ ScratchFile profile;
+
+ ProfileCompilationInfo saved_info;
+ ProfileCompilationInfo::OfflineProfileMethodInfo pmi = GetOfflineProfileMethodInfo();
+
+ // Add methods with inline caches.
+ for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+ // Add a method which is part of the same dex file as one of the
+ // class from the inline caches.
+ ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, method_idx, pmi, &saved_info));
+ // Add a method which is outside the set of dex files.
+ ASSERT_TRUE(AddMethod("dex_location4", /* checksum */ 4, method_idx, pmi, &saved_info));
+ }
+
+ ASSERT_TRUE(saved_info.Save(GetFd(profile)));
+ ASSERT_EQ(0, profile.GetFile()->Flush());
+
+ // Check that we get back what we saved.
+ ProfileCompilationInfo loaded_info;
+ ASSERT_TRUE(profile.GetFile()->ResetOffset());
+ ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
+
+ ASSERT_TRUE(loaded_info.Equals(saved_info));
+
+ ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi1;
+ ASSERT_TRUE(loaded_info.GetMethod("dex_location1",
+ /* checksum */ 1,
+ /* method_idx */ 3,
+ &loaded_pmi1));
+ ASSERT_TRUE(loaded_pmi1 == pmi);
+ ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi2;
+ ASSERT_TRUE(loaded_info.GetMethod("dex_location4",
+ /* checksum */ 4,
+ /* method_idx */ 3,
+ &loaded_pmi2));
+ ASSERT_TRUE(loaded_pmi2 == pmi);
+}
+
+TEST_F(ProfileCompilationInfoTest, MegamorphicInlineCaches) {
+ ScratchFile profile;
+
+ ProfileCompilationInfo saved_info;
+ ProfileCompilationInfo::OfflineProfileMethodInfo pmi = GetOfflineProfileMethodInfo();
+
+ // Add methods with inline caches.
+ for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+ ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, method_idx, pmi, &saved_info));
+ }
+
+ ASSERT_TRUE(saved_info.Save(GetFd(profile)));
+ ASSERT_EQ(0, profile.GetFile()->Flush());
+
+ // Make the inline caches megamorphic and add them to the profile again.
+ ProfileCompilationInfo saved_info_extra;
+ ProfileCompilationInfo::OfflineProfileMethodInfo pmi_extra = GetOfflineProfileMethodInfo();
+ MakeMegamorphic(&pmi_extra);
+ for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+ ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, method_idx, pmi, &saved_info_extra));
+ }
+
+ ASSERT_TRUE(profile.GetFile()->ResetOffset());
+ ASSERT_TRUE(saved_info_extra.Save(GetFd(profile)));
+ ASSERT_EQ(0, profile.GetFile()->Flush());
+
+ // Merge the profiles so that we have the same view as the file.
+ ASSERT_TRUE(saved_info.MergeWith(saved_info_extra));
+
+ // Check that we get back what we saved.
+ ProfileCompilationInfo loaded_info;
+ ASSERT_TRUE(profile.GetFile()->ResetOffset());
+ ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
+
+ ASSERT_TRUE(loaded_info.Equals(saved_info));
+
+ ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi1;
+ ASSERT_TRUE(loaded_info.GetMethod("dex_location1",
+ /* checksum */ 1,
+ /* method_idx */ 3,
+ &loaded_pmi1));
+ ASSERT_TRUE(loaded_pmi1 == pmi_extra);
+}
+
+TEST_F(ProfileCompilationInfoTest, SaveArtMethodsWithInlineCaches) {
+ ScratchFile profile;
+
+ Thread* self = Thread::Current();
+ jobject class_loader;
+ {
+ ScopedObjectAccess soa(self);
+ class_loader = LoadDex("ProfileTestMultiDex");
+ }
+ ASSERT_NE(class_loader, nullptr);
+
+ // Save virtual methods from Main.
+ std::set<DexCacheResolvedClasses> resolved_classes;
+ std::vector<ArtMethod*> main_methods = GetVirtualMethods(class_loader, "LMain;");
+
+ SafeMap<ArtMethod*, ProfileMethodInfo> profile_methods_map;
+ ASSERT_TRUE(SaveProfilingInfoWithFakeInlineCaches(
+ profile.GetFilename(), main_methods, &profile_methods_map));
+
+ // Check that what we saved is in the profile.
+ ProfileCompilationInfo info;
+ ASSERT_TRUE(info.Load(GetFd(profile)));
+ ASSERT_EQ(info.GetNumberOfMethods(), main_methods.size());
+ {
+ ScopedObjectAccess soa(self);
+ for (ArtMethod* m : main_methods) {
+ ASSERT_TRUE(info.ContainsMethod(MethodReference(m->GetDexFile(), m->GetDexMethodIndex())));
+ const ProfileMethodInfo& pmi = profile_methods_map.find(m)->second;
+ ProfileCompilationInfo::OfflineProfileMethodInfo offline_pmi;
+ ASSERT_TRUE(info.GetMethod(m->GetDexFile()->GetLocation(),
+ m->GetDexFile()->GetLocationChecksum(),
+ m->GetDexMethodIndex(),
+ &offline_pmi));
+ ProfileCompilationInfo::OfflineProfileMethodInfo converted_pmi =
+ ConvertProfileMethodInfo(pmi);
+ ASSERT_EQ(converted_pmi, offline_pmi);
+ }
+ }
+}
+
+TEST_F(ProfileCompilationInfoTest, InvalidChecksumInInlineCahce) {
+ ScratchFile profile;
+
+ ProfileCompilationInfo info;
+ ProfileCompilationInfo::OfflineProfileMethodInfo pmi1 = GetOfflineProfileMethodInfo();
+ ProfileCompilationInfo::OfflineProfileMethodInfo pmi2 = GetOfflineProfileMethodInfo();
+ // Modify the checksum to trigger a mismatch.
+ pmi2.dex_references[0].dex_checksum++;
+
+ ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /*method_idx*/ 0, pmi1, &info));
+ ASSERT_FALSE(AddMethod("dex_location2", /* checksum */ 2, /*method_idx*/ 0, pmi2, &info));
+}
+
+// Verify that profiles behave correctly even if the methods are added in a different
+// order and with a different dex profile indices for the dex files.
+TEST_F(ProfileCompilationInfoTest, MergeInlineCacheTriggerReindex) {
+ ScratchFile profile;
+
+ ProfileCompilationInfo info;
+ ProfileCompilationInfo info_reindexed;
+
+ ProfileCompilationInfo::OfflineProfileMethodInfo pmi;
+ pmi.dex_references.emplace_back("dex_location1", /* checksum */ 1);
+ pmi.dex_references.emplace_back("dex_location2", /* checksum */ 2);
+ for (uint16_t dex_pc = 1; dex_pc < 5; dex_pc++) {
+ ProfileCompilationInfo::DexPcData dex_pc_data;
+ dex_pc_data.AddClass(0, dex::TypeIndex(0));
+ dex_pc_data.AddClass(1, dex::TypeIndex(1));
+ pmi.inline_caches.Put(dex_pc, dex_pc_data);
+ }
+
+ ProfileCompilationInfo::OfflineProfileMethodInfo pmi_reindexed;
+ pmi_reindexed.dex_references.emplace_back("dex_location2", /* checksum */ 2);
+ pmi_reindexed.dex_references.emplace_back("dex_location1", /* checksum */ 1);
+ for (uint16_t dex_pc = 1; dex_pc < 5; dex_pc++) {
+ ProfileCompilationInfo::DexPcData dex_pc_data;
+ dex_pc_data.AddClass(1, dex::TypeIndex(0));
+ dex_pc_data.AddClass(0, dex::TypeIndex(1));
+ pmi_reindexed.inline_caches.Put(dex_pc, dex_pc_data);
+ }
+
+ // Profile 1 and Profile 2 get the same methods but in different order.
+ // This will trigger a different dex numbers.
+ for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+ ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, method_idx, pmi, &info));
+ ASSERT_TRUE(AddMethod("dex_location2", /* checksum */ 2, method_idx, pmi, &info));
+ }
+
+ for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+ ASSERT_TRUE(AddMethod(
+ "dex_location2", /* checksum */ 2, method_idx, pmi_reindexed, &info_reindexed));
+ ASSERT_TRUE(AddMethod(
+ "dex_location1", /* checksum */ 1, method_idx, pmi_reindexed, &info_reindexed));
+ }
+
+ ProfileCompilationInfo info_backup = info;
+ ASSERT_TRUE(info.MergeWith(info_reindexed));
+ // Merging should have no effect as we're adding the exact same stuff.
+ ASSERT_TRUE(info.Equals(info_backup));
+ for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+ ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi1;
+ ASSERT_TRUE(info.GetMethod("dex_location1",
+ /* checksum */ 1,
+ /* method_idx */ method_idx,
+ &loaded_pmi1));
+ ASSERT_TRUE(loaded_pmi1 == pmi);
+ ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi2;
+ ASSERT_TRUE(info.GetMethod("dex_location2",
+ /* checksum */ 2,
+ /* method_idx */ method_idx,
+ &loaded_pmi2));
+ ASSERT_TRUE(loaded_pmi2 == pmi);
+ }
+}
+
+TEST_F(ProfileCompilationInfoTest, AddMoreDexFileThanLimit) {
+ ProfileCompilationInfo info;
+ // Save a few methods.
+ for (uint16_t i = 0; i < std::numeric_limits<uint8_t>::max(); i++) {
+ std::string dex_location = std::to_string(i);
+ ASSERT_TRUE(AddMethod(dex_location, /* checksum */ 1, /* method_idx */ i, &info));
+ }
+ // We only support at most 255 dex files.
+ ASSERT_FALSE(AddMethod(
+ /*dex_location*/ "256", /* checksum */ 1, /* method_idx */ 0, &info));
+}
+
+TEST_F(ProfileCompilationInfoTest, MegamorphicInlineCachesMerge) {
+ // Create a megamorphic inline cache.
+ ProfileCompilationInfo::OfflineProfileMethodInfo pmi;
+ pmi.dex_references.emplace_back("dex_location1", /* checksum */ 1);
+ ProfileCompilationInfo::DexPcData dex_pc_data;
+ dex_pc_data.is_megamorphic = true;
+ pmi.inline_caches.Put(/*dex_pc*/ 0, dex_pc_data);
+
+ ProfileCompilationInfo info_megamorphic;
+ ASSERT_TRUE(AddMethod("dex_location1",
+ /*checksum*/ 1,
+ /*method_idx*/ 0,
+ pmi,
+ &info_megamorphic));
+
+ // Create a profile with no inline caches (for the same method).
+ ProfileCompilationInfo info_no_inline_cache;
+ ASSERT_TRUE(AddMethod("dex_location1",
+ /*checksum*/ 1,
+ /*method_idx*/ 0,
+ &info_no_inline_cache));
+
+ // Merge the megamorphic cache into the empty one.
+ ASSERT_TRUE(info_no_inline_cache.MergeWith(info_megamorphic));
+ ScratchFile profile;
+ // Saving profile should work without crashing (b/35644850).
+ ASSERT_TRUE(info_no_inline_cache.Save(GetFd(profile)));
+}
+
} // namespace art
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index 025d10c..61e6c41 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -236,10 +236,10 @@
std::set<DexCacheResolvedClasses> resolved_classes_for_location;
const std::string& filename = it.first;
const std::set<std::string>& locations = it.second;
- std::vector<MethodReference> methods_for_location;
+ std::vector<ProfileMethodInfo> profile_methods_for_location;
for (const MethodReference& ref : methods) {
if (locations.find(ref.dex_file->GetBaseLocation()) != locations.end()) {
- methods_for_location.push_back(ref);
+ profile_methods_for_location.emplace_back(ref.dex_file, ref.dex_method_index);
}
}
for (const DexCacheResolvedClasses& classes : resolved_classes) {
@@ -253,7 +253,7 @@
}
}
ProfileCompilationInfo* info = GetCachedProfiledInfo(filename);
- info->AddMethodsAndClasses(methods_for_location, resolved_classes_for_location);
+ info->AddMethodsAndClasses(profile_methods_for_location, resolved_classes_for_location);
total_number_of_profile_entries_cached += resolved_classes_for_location.size();
}
max_number_of_profile_entries_cached_ = std::max(
@@ -280,15 +280,15 @@
}
const std::string& filename = it.first;
const std::set<std::string>& locations = it.second;
- std::vector<MethodReference> methods;
+ std::vector<ProfileMethodInfo> profile_methods;
{
ScopedObjectAccess soa(Thread::Current());
- jit_code_cache_->GetProfiledMethods(locations, methods);
+ jit_code_cache_->GetProfiledMethods(locations, profile_methods);
total_number_of_code_cache_queries_++;
}
ProfileCompilationInfo* cached_info = GetCachedProfiledInfo(filename);
- cached_info->AddMethodsAndClasses(methods, std::set<DexCacheResolvedClasses>());
+ cached_info->AddMethodsAndClasses(profile_methods, std::set<DexCacheResolvedClasses>());
int64_t delta_number_of_methods =
cached_info->GetNumberOfMethods() -
static_cast<int64_t>(last_save_number_of_methods_);
diff --git a/runtime/jit/profiling_info.h b/runtime/jit/profiling_info.h
index 1c58a83..f42a8da 100644
--- a/runtime/jit/profiling_info.h
+++ b/runtime/jit/profiling_info.h
@@ -39,7 +39,7 @@
// Once the classes_ array is full, we consider the INVOKE to be megamorphic.
class InlineCache {
public:
- static constexpr uint16_t kIndividualCacheSize = 5;
+ static constexpr uint8_t kIndividualCacheSize = 5;
private:
uint32_t dex_pc_;
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index 547b5b8..5418d35 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -2265,7 +2265,18 @@
VLOG(jni) << "[Registering JNI native method " << m->PrettyMethod() << "]";
- is_fast = is_fast || m->IsFastNative(); // Merge with @FastNative state.
+ if (UNLIKELY(is_fast)) {
+ // There are a few reasons to switch:
+ // 1) We don't support !bang JNI anymore, it will turn to a hard error later.
+ // 2) @FastNative is actually faster. At least 1.5x faster than !bang JNI.
+ // and switching is super easy, remove ! in C code, add annotation in .java code.
+ // 3) Good chance of hitting DCHECK failures in ScopedFastNativeObjectAccess
+ // since that checks for presence of @FastNative and not for ! in the descriptor.
+ LOG(WARNING) << "!bang JNI is deprecated. Switch to @FastNative for " << m->PrettyMethod();
+ is_fast = false;
+ // TODO: make this a hard register error in the future.
+ }
+
m->RegisterNative(fnPtr, is_fast);
}
return JNI_OK;
diff --git a/runtime/jni_internal.h b/runtime/jni_internal.h
index b3837c4..580a42b 100644
--- a/runtime/jni_internal.h
+++ b/runtime/jni_internal.h
@@ -19,20 +19,10 @@
#include <jni.h>
#include <iosfwd>
+#include "nativehelper/jni_macros.h"
#include "base/macros.h"
-#ifndef NATIVE_METHOD
-#define NATIVE_METHOD(className, functionName, signature) \
- { #functionName, signature, reinterpret_cast<void*>(className ## _ ## functionName) }
-#endif
-
-// TODO: Can we do a better job of supporting overloading ?
-#ifndef OVERLOADED_NATIVE_METHOD
-#define OVERLOADED_NATIVE_METHOD(className, functionName, signature, identifier) \
- { #functionName, signature, reinterpret_cast<void*>(className ## _ ## identifier) }
-#endif
-
#define REGISTER_NATIVE_METHODS(jni_class_name) \
RegisterNativeMethods(env, jni_class_name, gMethods, arraysize(gMethods))
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 9a9a5d8..eb2ec9b 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -951,7 +951,8 @@
return interfaces->Get(idx);
} else {
dex::TypeIndex type_idx = klass->GetDirectInterfaceTypeIdx(idx);
- ObjPtr<Class> interface = klass->GetDexCache()->GetResolvedType(type_idx);
+ ObjPtr<Class> interface = ClassLinker::LookupResolvedType(
+ type_idx, klass->GetDexCache(), klass->GetClassLoader());
return interface;
}
}
diff --git a/runtime/mirror/dex_cache-inl.h b/runtime/mirror/dex_cache-inl.h
index 973c8ed..29bf6a0 100644
--- a/runtime/mirror/dex_cache-inl.h
+++ b/runtime/mirror/dex_cache-inl.h
@@ -41,14 +41,22 @@
return Class::ComputeClassSize(true, vtable_entries, 0, 0, 0, 0, 0, pointer_size);
}
-inline mirror::String* DexCache::GetResolvedString(dex::StringIndex string_idx) {
+inline uint32_t DexCache::StringSlotIndex(dex::StringIndex string_idx) {
DCHECK_LT(string_idx.index_, GetDexFile()->NumStringIds());
- return StringDexCachePair::Lookup(GetStrings(), string_idx.index_, NumStrings()).Read();
+ const uint32_t slot_idx = string_idx.index_ % kDexCacheStringCacheSize;
+ DCHECK_LT(slot_idx, NumStrings());
+ return slot_idx;
}
-inline void DexCache::SetResolvedString(dex::StringIndex string_idx,
- ObjPtr<mirror::String> resolved) {
- StringDexCachePair::Assign(GetStrings(), string_idx.index_, resolved.Ptr(), NumStrings());
+inline String* DexCache::GetResolvedString(dex::StringIndex string_idx) {
+ return GetStrings()[StringSlotIndex(string_idx)].load(
+ std::memory_order_relaxed).GetObjectForIndex(string_idx.index_);
+}
+
+inline void DexCache::SetResolvedString(dex::StringIndex string_idx, ObjPtr<String> resolved) {
+ DCHECK(resolved != nullptr);
+ GetStrings()[StringSlotIndex(string_idx)].store(
+ StringDexCachePair(resolved, string_idx.index_), std::memory_order_relaxed);
Runtime* const runtime = Runtime::Current();
if (UNLIKELY(runtime->IsActiveTransaction())) {
DCHECK(runtime->IsAotCompiler());
@@ -59,50 +67,70 @@
}
inline void DexCache::ClearString(dex::StringIndex string_idx) {
- const uint32_t slot_idx = string_idx.index_ % NumStrings();
DCHECK(Runtime::Current()->IsAotCompiler());
+ uint32_t slot_idx = StringSlotIndex(string_idx);
StringDexCacheType* slot = &GetStrings()[slot_idx];
// This is racy but should only be called from the transactional interpreter.
if (slot->load(std::memory_order_relaxed).index == string_idx.index_) {
- StringDexCachePair cleared(
- nullptr,
- StringDexCachePair::InvalidIndexForSlot(slot_idx));
+ StringDexCachePair cleared(nullptr, StringDexCachePair::InvalidIndexForSlot(slot_idx));
slot->store(cleared, std::memory_order_relaxed);
}
}
+inline uint32_t DexCache::TypeSlotIndex(dex::TypeIndex type_idx) {
+ DCHECK_LT(type_idx.index_, GetDexFile()->NumTypeIds());
+ const uint32_t slot_idx = type_idx.index_ % kDexCacheTypeCacheSize;
+ DCHECK_LT(slot_idx, NumResolvedTypes());
+ return slot_idx;
+}
+
inline Class* DexCache::GetResolvedType(dex::TypeIndex type_idx) {
// It is theorized that a load acquire is not required since obtaining the resolved class will
// always have an address dependency or a lock.
- DCHECK_LT(type_idx.index_, NumResolvedTypes());
- return GetResolvedTypes()[type_idx.index_].Read();
+ return GetResolvedTypes()[TypeSlotIndex(type_idx)].load(
+ std::memory_order_relaxed).GetObjectForIndex(type_idx.index_);
}
inline void DexCache::SetResolvedType(dex::TypeIndex type_idx, ObjPtr<Class> resolved) {
- DCHECK_LT(type_idx.index_, NumResolvedTypes()); // NOTE: Unchecked, i.e. not throwing AIOOB.
+ DCHECK(resolved != nullptr);
// TODO default transaction support.
// Use a release store for SetResolvedType. This is done to prevent other threads from seeing a
// class but not necessarily seeing the loaded members like the static fields array.
// See b/32075261.
- reinterpret_cast<Atomic<GcRoot<mirror::Class>>&>(GetResolvedTypes()[type_idx.index_]).
- StoreRelease(GcRoot<Class>(resolved));
+ GetResolvedTypes()[TypeSlotIndex(type_idx)].store(
+ TypeDexCachePair(resolved, type_idx.index_), std::memory_order_release);
// TODO: Fine-grained marking, so that we don't need to go through all arrays in full.
Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(this);
}
-inline MethodType* DexCache::GetResolvedMethodType(uint32_t proto_idx) {
+inline void DexCache::ClearResolvedType(dex::TypeIndex type_idx) {
+ DCHECK(Runtime::Current()->IsAotCompiler());
+ uint32_t slot_idx = TypeSlotIndex(type_idx);
+ TypeDexCacheType* slot = &GetResolvedTypes()[slot_idx];
+ // This is racy but should only be called from the single-threaded ImageWriter and tests.
+ if (slot->load(std::memory_order_relaxed).index == type_idx.index_) {
+ TypeDexCachePair cleared(nullptr, TypeDexCachePair::InvalidIndexForSlot(slot_idx));
+ slot->store(cleared, std::memory_order_relaxed);
+ }
+}
+
+inline uint32_t DexCache::MethodTypeSlotIndex(uint32_t proto_idx) {
DCHECK(Runtime::Current()->IsMethodHandlesEnabled());
DCHECK_LT(proto_idx, GetDexFile()->NumProtoIds());
- return MethodTypeDexCachePair::Lookup(
- GetResolvedMethodTypes(), proto_idx, NumResolvedMethodTypes()).Read();
+ const uint32_t slot_idx = proto_idx % kDexCacheMethodTypeCacheSize;
+ DCHECK_LT(slot_idx, NumResolvedMethodTypes());
+ return slot_idx;
+}
+
+inline MethodType* DexCache::GetResolvedMethodType(uint32_t proto_idx) {
+ return GetResolvedMethodTypes()[MethodTypeSlotIndex(proto_idx)].load(
+ std::memory_order_relaxed).GetObjectForIndex(proto_idx);
}
inline void DexCache::SetResolvedMethodType(uint32_t proto_idx, MethodType* resolved) {
- DCHECK(Runtime::Current()->IsMethodHandlesEnabled());
- DCHECK_LT(proto_idx, GetDexFile()->NumProtoIds());
-
- MethodTypeDexCachePair::Assign(GetResolvedMethodTypes(), proto_idx, resolved,
- NumResolvedMethodTypes());
+ DCHECK(resolved != nullptr);
+ GetResolvedMethodTypes()[MethodTypeSlotIndex(proto_idx)].store(
+ MethodTypeDexCachePair(resolved, proto_idx), std::memory_order_relaxed);
// TODO: Fine-grained marking, so that we don't need to go through all arrays in full.
Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(this);
}
@@ -228,15 +256,13 @@
VisitInstanceFieldsReferences<kVerifyFlags, kReadBarrierOption>(klass, visitor);
// Visit arrays after.
if (kVisitNativeRoots) {
- VisitDexCachePairs<mirror::String, kReadBarrierOption, Visitor>(
+ VisitDexCachePairs<String, kReadBarrierOption, Visitor>(
GetStrings(), NumStrings(), visitor);
- GcRoot<mirror::Class>* resolved_types = GetResolvedTypes();
- for (size_t i = 0, num_types = NumResolvedTypes(); i != num_types; ++i) {
- visitor.VisitRootIfNonNull(resolved_types[i].AddressWithoutBarrier());
- }
+ VisitDexCachePairs<Class, kReadBarrierOption, Visitor>(
+ GetResolvedTypes(), NumResolvedTypes(), visitor);
- VisitDexCachePairs<mirror::MethodType, kReadBarrierOption, Visitor>(
+ VisitDexCachePairs<MethodType, kReadBarrierOption, Visitor>(
GetResolvedMethodTypes(), NumResolvedMethodTypes(), visitor);
GcRoot<mirror::CallSite>* resolved_call_sites = GetResolvedCallSites();
@@ -247,35 +273,37 @@
}
template <ReadBarrierOption kReadBarrierOption, typename Visitor>
-inline void DexCache::FixupStrings(mirror::StringDexCacheType* dest, const Visitor& visitor) {
- mirror::StringDexCacheType* src = GetStrings();
+inline void DexCache::FixupStrings(StringDexCacheType* dest, const Visitor& visitor) {
+ StringDexCacheType* src = GetStrings();
for (size_t i = 0, count = NumStrings(); i < count; ++i) {
StringDexCachePair source = src[i].load(std::memory_order_relaxed);
- mirror::String* ptr = source.object.Read<kReadBarrierOption>();
- mirror::String* new_source = visitor(ptr);
+ String* ptr = source.object.Read<kReadBarrierOption>();
+ String* new_source = visitor(ptr);
source.object = GcRoot<String>(new_source);
dest[i].store(source, std::memory_order_relaxed);
}
}
template <ReadBarrierOption kReadBarrierOption, typename Visitor>
-inline void DexCache::FixupResolvedTypes(GcRoot<mirror::Class>* dest, const Visitor& visitor) {
- GcRoot<mirror::Class>* src = GetResolvedTypes();
+inline void DexCache::FixupResolvedTypes(TypeDexCacheType* dest, const Visitor& visitor) {
+ TypeDexCacheType* src = GetResolvedTypes();
for (size_t i = 0, count = NumResolvedTypes(); i < count; ++i) {
- mirror::Class* source = src[i].Read<kReadBarrierOption>();
- mirror::Class* new_source = visitor(source);
- dest[i] = GcRoot<mirror::Class>(new_source);
+ TypeDexCachePair source = src[i].load(std::memory_order_relaxed);
+ Class* ptr = source.object.Read<kReadBarrierOption>();
+ Class* new_source = visitor(ptr);
+ source.object = GcRoot<Class>(new_source);
+ dest[i].store(source, std::memory_order_relaxed);
}
}
template <ReadBarrierOption kReadBarrierOption, typename Visitor>
-inline void DexCache::FixupResolvedMethodTypes(mirror::MethodTypeDexCacheType* dest,
+inline void DexCache::FixupResolvedMethodTypes(MethodTypeDexCacheType* dest,
const Visitor& visitor) {
- mirror::MethodTypeDexCacheType* src = GetResolvedMethodTypes();
+ MethodTypeDexCacheType* src = GetResolvedMethodTypes();
for (size_t i = 0, count = NumResolvedMethodTypes(); i < count; ++i) {
MethodTypeDexCachePair source = src[i].load(std::memory_order_relaxed);
- mirror::MethodType* ptr = source.object.Read<kReadBarrierOption>();
- mirror::MethodType* new_source = visitor(ptr);
+ MethodType* ptr = source.object.Read<kReadBarrierOption>();
+ MethodType* new_source = visitor(ptr);
source.object = GcRoot<MethodType>(new_source);
dest[i].store(source, std::memory_order_relaxed);
}
diff --git a/runtime/mirror/dex_cache.cc b/runtime/mirror/dex_cache.cc
index 0f6acab..1b8b391 100644
--- a/runtime/mirror/dex_cache.cc
+++ b/runtime/mirror/dex_cache.cc
@@ -58,8 +58,8 @@
mirror::StringDexCacheType* strings = (dex_file->NumStringIds() == 0u) ? nullptr :
reinterpret_cast<mirror::StringDexCacheType*>(raw_arrays + layout.StringsOffset());
- GcRoot<mirror::Class>* types = (dex_file->NumTypeIds() == 0u) ? nullptr :
- reinterpret_cast<GcRoot<mirror::Class>*>(raw_arrays + layout.TypesOffset());
+ mirror::TypeDexCacheType* types = (dex_file->NumTypeIds() == 0u) ? nullptr :
+ reinterpret_cast<mirror::TypeDexCacheType*>(raw_arrays + layout.TypesOffset());
ArtMethod** methods = (dex_file->NumMethodIds() == 0u) ? nullptr :
reinterpret_cast<ArtMethod**>(raw_arrays + layout.MethodsOffset());
ArtField** fields = (dex_file->NumFieldIds() == 0u) ? nullptr :
@@ -69,6 +69,10 @@
if (dex_file->NumStringIds() < num_strings) {
num_strings = dex_file->NumStringIds();
}
+ size_t num_types = mirror::DexCache::kDexCacheTypeCacheSize;
+ if (dex_file->NumTypeIds() < num_types) {
+ num_types = dex_file->NumTypeIds();
+ }
// Note that we allocate the method type dex caches regardless of this flag,
// and we make sure here that they're not used by the runtime. This is in the
@@ -108,8 +112,9 @@
CHECK_EQ(strings[i].load(std::memory_order_relaxed).index, 0u);
CHECK(strings[i].load(std::memory_order_relaxed).object.IsNull());
}
- for (size_t i = 0; i < dex_file->NumTypeIds(); ++i) {
- CHECK(types[i].IsNull());
+ for (size_t i = 0; i < num_types; ++i) {
+ CHECK_EQ(types[i].load(std::memory_order_relaxed).index, 0u);
+ CHECK(types[i].load(std::memory_order_relaxed).object.IsNull());
}
for (size_t i = 0; i < dex_file->NumMethodIds(); ++i) {
CHECK(mirror::DexCache::GetElementPtrSize(methods, i, image_pointer_size) == nullptr);
@@ -128,6 +133,9 @@
if (strings != nullptr) {
mirror::StringDexCachePair::Initialize(strings);
}
+ if (types != nullptr) {
+ mirror::TypeDexCachePair::Initialize(types);
+ }
if (method_types != nullptr) {
mirror::MethodTypeDexCachePair::Initialize(method_types);
}
@@ -136,7 +144,7 @@
strings,
num_strings,
types,
- dex_file->NumTypeIds(),
+ num_types,
methods,
dex_file->NumMethodIds(),
fields,
@@ -152,7 +160,7 @@
ObjPtr<String> location,
StringDexCacheType* strings,
uint32_t num_strings,
- GcRoot<Class>* resolved_types,
+ TypeDexCacheType* resolved_types,
uint32_t num_resolved_types,
ArtMethod** resolved_methods,
uint32_t num_resolved_methods,
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index 10bb5aa..0579198 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -18,14 +18,14 @@
#define ART_RUNTIME_MIRROR_DEX_CACHE_H_
#include "array.h"
-#include "art_field.h"
-#include "class.h"
+#include "base/bit_utils.h"
#include "dex_file_types.h"
#include "object.h"
#include "object_array.h"
namespace art {
+class ArtField;
class ArtMethod;
struct DexCacheOffsets;
class DexFile;
@@ -37,6 +37,7 @@
namespace mirror {
class CallSite;
+class Class;
class MethodType;
class String;
@@ -61,7 +62,7 @@
// it's always non-null if the id branch succeeds (except for the 0th id).
// Set the initial state for the 0th entry to be {0,1} which is guaranteed to fail
// the lookup id == stored id branch.
- DexCachePair(T* object, uint32_t index)
+ DexCachePair(ObjPtr<T> object, uint32_t index)
: object(object),
index(index) {}
DexCachePair() = default;
@@ -75,39 +76,28 @@
dex_cache[0].store(first_elem, std::memory_order_relaxed);
}
- static GcRoot<T> Lookup(std::atomic<DexCachePair<T>>* dex_cache,
- uint32_t idx,
- uint32_t cache_size) {
- DCHECK_NE(cache_size, 0u);
- DexCachePair<T> element = dex_cache[idx % cache_size].load(std::memory_order_relaxed);
- if (idx != element.index) {
- return GcRoot<T>(nullptr);
- }
-
- DCHECK(!element.object.IsNull());
- return element.object;
- }
-
- static void Assign(std::atomic<DexCachePair<T>>* dex_cache,
- uint32_t idx,
- T* object,
- uint32_t cache_size) {
- DCHECK_LT(idx % cache_size, cache_size);
- dex_cache[idx % cache_size].store(
- DexCachePair<T>(object, idx), std::memory_order_relaxed);
- }
-
static uint32_t InvalidIndexForSlot(uint32_t slot) {
// Since the cache size is a power of two, 0 will always map to slot 0.
// Use 1 for slot 0 and 0 for all other slots.
return (slot == 0) ? 1u : 0u;
}
+
+ T* GetObjectForIndex(uint32_t idx) REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (idx != index) {
+ return nullptr;
+ }
+ DCHECK(!object.IsNull());
+ return object.Read();
+ }
};
-using StringDexCachePair = DexCachePair<mirror::String>;
+using TypeDexCachePair = DexCachePair<Class>;
+using TypeDexCacheType = std::atomic<TypeDexCachePair>;
+
+using StringDexCachePair = DexCachePair<String>;
using StringDexCacheType = std::atomic<StringDexCachePair>;
-using MethodTypeDexCachePair = DexCachePair<mirror::MethodType>;
+using MethodTypeDexCachePair = DexCachePair<MethodType>;
using MethodTypeDexCacheType = std::atomic<MethodTypeDexCachePair>;
// C++ mirror of java.lang.DexCache.
@@ -116,6 +106,11 @@
// Size of java.lang.DexCache.class.
static uint32_t ClassSize(PointerSize pointer_size);
+ // Size of type dex cache. Needs to be a power of 2 for entrypoint assumptions to hold.
+ static constexpr size_t kDexCacheTypeCacheSize = 1024;
+ static_assert(IsPowerOfTwo(kDexCacheTypeCacheSize),
+ "Type dex cache size is not a power of 2.");
+
// Size of string dex cache. Needs to be a power of 2 for entrypoint assumptions to hold.
static constexpr size_t kDexCacheStringCacheSize = 1024;
static_assert(IsPowerOfTwo(kDexCacheStringCacheSize),
@@ -127,6 +122,10 @@
static_assert(IsPowerOfTwo(kDexCacheMethodTypeCacheSize),
"MethodType dex cache size is not a power of 2.");
+ static constexpr size_t StaticTypeSize() {
+ return kDexCacheTypeCacheSize;
+ }
+
static constexpr size_t StaticStringSize() {
return kDexCacheStringCacheSize;
}
@@ -157,7 +156,7 @@
REQUIRES_SHARED(Locks::mutator_lock_);
template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier, typename Visitor>
- void FixupResolvedTypes(GcRoot<mirror::Class>* dest, const Visitor& visitor)
+ void FixupResolvedTypes(TypeDexCacheType* dest, const Visitor& visitor)
REQUIRES_SHARED(Locks::mutator_lock_);
template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier, typename Visitor>
@@ -224,7 +223,7 @@
return OFFSET_OF_OBJECT_MEMBER(DexCache, num_resolved_call_sites_);
}
- mirror::String* GetResolvedString(dex::StringIndex string_idx) ALWAYS_INLINE
+ String* GetResolvedString(dex::StringIndex string_idx) ALWAYS_INLINE
REQUIRES_SHARED(Locks::mutator_lock_);
void SetResolvedString(dex::StringIndex string_idx, ObjPtr<mirror::String> resolved) ALWAYS_INLINE
@@ -239,6 +238,8 @@
void SetResolvedType(dex::TypeIndex type_idx, ObjPtr<Class> resolved)
REQUIRES_SHARED(Locks::mutator_lock_);
+ void ClearResolvedType(dex::TypeIndex type_idx) REQUIRES_SHARED(Locks::mutator_lock_);
+
ALWAYS_INLINE ArtMethod* GetResolvedMethod(uint32_t method_idx, PointerSize ptr_size)
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -278,11 +279,11 @@
SetFieldPtr<false>(StringsOffset(), strings);
}
- GcRoot<Class>* GetResolvedTypes() ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
- return GetFieldPtr<GcRoot<Class>*>(ResolvedTypesOffset());
+ TypeDexCacheType* GetResolvedTypes() ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
+ return GetFieldPtr<TypeDexCacheType*>(ResolvedTypesOffset());
}
- void SetResolvedTypes(GcRoot<Class>* resolved_types)
+ void SetResolvedTypes(TypeDexCacheType* resolved_types)
ALWAYS_INLINE
REQUIRES_SHARED(Locks::mutator_lock_) {
SetFieldPtr<false>(ResolvedTypesOffset(), resolved_types);
@@ -363,7 +364,7 @@
SetFieldPtr<false>(OFFSET_OF_OBJECT_MEMBER(DexCache, dex_file_), dex_file);
}
- void SetLocation(ObjPtr<mirror::String> location) REQUIRES_SHARED(Locks::mutator_lock_);
+ void SetLocation(ObjPtr<String> location) REQUIRES_SHARED(Locks::mutator_lock_);
// NOTE: Get/SetElementPtrSize() are intended for working with ArtMethod** and ArtField**
// provided by GetResolvedMethods/Fields() and ArtMethod::GetDexCacheResolvedMethods(),
@@ -380,7 +381,7 @@
ObjPtr<String> location,
StringDexCacheType* strings,
uint32_t num_strings,
- GcRoot<Class>* resolved_types,
+ TypeDexCacheType* resolved_types,
uint32_t num_resolved_types,
ArtMethod** resolved_methods,
uint32_t num_resolved_methods,
@@ -393,12 +394,16 @@
PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
+ uint32_t StringSlotIndex(dex::StringIndex string_idx) REQUIRES_SHARED(Locks::mutator_lock_);
+ uint32_t TypeSlotIndex(dex::TypeIndex type_idx) REQUIRES_SHARED(Locks::mutator_lock_);
+ uint32_t MethodTypeSlotIndex(uint32_t proto_idx) REQUIRES_SHARED(Locks::mutator_lock_);
+
// Visit instance fields of the dex cache as well as its associated arrays.
template <bool kVisitNativeRoots,
VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
ReadBarrierOption kReadBarrierOption = kWithReadBarrier,
typename Visitor>
- void VisitReferences(ObjPtr<mirror::Class> klass, const Visitor& visitor)
+ void VisitReferences(ObjPtr<Class> klass, const Visitor& visitor)
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_);
HeapReference<Object> dex_;
@@ -410,7 +415,7 @@
uint64_t resolved_method_types_; // std::atomic<MethodTypeDexCachePair>* array with
// num_resolved_method_types_ elements.
uint64_t resolved_methods_; // ArtMethod*, array with num_resolved_methods_ elements.
- uint64_t resolved_types_; // GcRoot<Class>*, array with num_resolved_types_ elements.
+ uint64_t resolved_types_; // TypeDexCacheType*, array with num_resolved_types_ elements.
uint64_t strings_; // std::atomic<StringDexCachePair>*, array with num_strings_
// elements.
diff --git a/runtime/mirror/dex_cache_test.cc b/runtime/mirror/dex_cache_test.cc
index 5a2ab71..ef0aaaa 100644
--- a/runtime/mirror/dex_cache_test.cc
+++ b/runtime/mirror/dex_cache_test.cc
@@ -51,7 +51,8 @@
EXPECT_TRUE(dex_cache->StaticStringSize() == dex_cache->NumStrings()
|| java_lang_dex_file_->NumStringIds() == dex_cache->NumStrings());
- EXPECT_EQ(java_lang_dex_file_->NumTypeIds(), dex_cache->NumResolvedTypes());
+ EXPECT_TRUE(dex_cache->StaticTypeSize() == dex_cache->NumResolvedTypes()
+ || java_lang_dex_file_->NumTypeIds() == dex_cache->NumResolvedTypes());
EXPECT_EQ(java_lang_dex_file_->NumMethodIds(), dex_cache->NumResolvedMethods());
EXPECT_EQ(java_lang_dex_file_->NumFieldIds(), dex_cache->NumResolvedFields());
EXPECT_TRUE(dex_cache->StaticMethodTypeSize() == dex_cache->NumResolvedMethodTypes()
diff --git a/runtime/mirror/object_test.cc b/runtime/mirror/object_test.cc
index e761e4d..d306f9c 100644
--- a/runtime/mirror/object_test.cc
+++ b/runtime/mirror/object_test.cc
@@ -726,57 +726,60 @@
ScopedObjectAccess soa(Thread::Current());
jobject jclass_loader = LoadDex("XandY");
StackHandleScope<2> hs(soa.Self());
- ObjPtr<mirror::Object, /*kPoison*/ true> null_ptr;
- EXPECT_TRUE(null_ptr.IsNull());
- EXPECT_TRUE(null_ptr.IsValid());
- EXPECT_TRUE(null_ptr.Ptr() == nullptr);
- EXPECT_TRUE(null_ptr == nullptr);
- EXPECT_TRUE(null_ptr == null_ptr);
- EXPECT_FALSE(null_ptr != null_ptr);
- EXPECT_FALSE(null_ptr != nullptr);
- null_ptr.AssertValid();
Handle<ClassLoader> class_loader(hs.NewHandle(soa.Decode<ClassLoader>(jclass_loader)));
Handle<mirror::Class> h_X(
hs.NewHandle(class_linker_->FindClass(soa.Self(), "LX;", class_loader)));
- ObjPtr<Class, /*kPoison*/ true> X(h_X.Get());
- EXPECT_TRUE(!X.IsNull());
- EXPECT_TRUE(X.IsValid());
- EXPECT_TRUE(X.Ptr() != nullptr);
- EXPECT_OBJ_PTR_EQ(h_X.Get(), X);
- // FindClass may cause thread suspension, it should invalidate X.
- ObjPtr<Class, /*kPoison*/ true> Y(class_linker_->FindClass(soa.Self(), "LY;", class_loader));
- EXPECT_TRUE(!Y.IsNull());
- EXPECT_TRUE(Y.IsValid());
- EXPECT_TRUE(Y.Ptr() != nullptr);
- // Should IsNull be safe to call on null ObjPtr? I'll allow it for now.
- EXPECT_TRUE(!X.IsNull());
- EXPECT_TRUE(!X.IsValid());
- // Make X valid again by copying out of handle.
- X.Assign(h_X.Get());
- EXPECT_TRUE(!X.IsNull());
- EXPECT_TRUE(X.IsValid());
- EXPECT_OBJ_PTR_EQ(h_X.Get(), X);
+ if (kObjPtrPoisoning) {
+ ObjPtr<mirror::Object> null_ptr;
+ EXPECT_TRUE(null_ptr.IsNull());
+ EXPECT_TRUE(null_ptr.IsValid());
+ EXPECT_TRUE(null_ptr.Ptr() == nullptr);
+ EXPECT_TRUE(null_ptr == nullptr);
+ EXPECT_TRUE(null_ptr == null_ptr);
+ EXPECT_FALSE(null_ptr != null_ptr);
+ EXPECT_FALSE(null_ptr != nullptr);
+ null_ptr.AssertValid();
+ ObjPtr<Class> X(h_X.Get());
+ EXPECT_TRUE(!X.IsNull());
+ EXPECT_TRUE(X.IsValid());
+ EXPECT_TRUE(X.Ptr() != nullptr);
+ EXPECT_OBJ_PTR_EQ(h_X.Get(), X);
+ // FindClass may cause thread suspension, it should invalidate X.
+ ObjPtr<Class> Y(class_linker_->FindClass(soa.Self(), "LY;", class_loader));
+ EXPECT_TRUE(!Y.IsNull());
+ EXPECT_TRUE(Y.IsValid());
+ EXPECT_TRUE(Y.Ptr() != nullptr);
- // Allow thread suspension to invalidate Y.
- soa.Self()->AllowThreadSuspension();
- EXPECT_TRUE(!Y.IsNull());
- EXPECT_TRUE(!Y.IsValid());
+ // Should IsNull be safe to call on null ObjPtr? I'll allow it for now.
+ EXPECT_TRUE(!X.IsNull());
+ EXPECT_TRUE(!X.IsValid());
+ // Make X valid again by copying out of handle.
+ X.Assign(h_X.Get());
+ EXPECT_TRUE(!X.IsNull());
+ EXPECT_TRUE(X.IsValid());
+ EXPECT_OBJ_PTR_EQ(h_X.Get(), X);
- // Test unpoisoned.
- ObjPtr<mirror::Object, /*kPoison*/ false> unpoisoned;
- EXPECT_TRUE(unpoisoned.IsNull());
- EXPECT_TRUE(unpoisoned.IsValid());
- EXPECT_TRUE(unpoisoned.Ptr() == nullptr);
- EXPECT_TRUE(unpoisoned == nullptr);
- EXPECT_TRUE(unpoisoned == unpoisoned);
- EXPECT_FALSE(unpoisoned != unpoisoned);
- EXPECT_FALSE(unpoisoned != nullptr);
+ // Allow thread suspension to invalidate Y.
+ soa.Self()->AllowThreadSuspension();
+ EXPECT_TRUE(!Y.IsNull());
+ EXPECT_TRUE(!Y.IsValid());
+ } else {
+ // Test unpoisoned.
+ ObjPtr<mirror::Object> unpoisoned;
+ EXPECT_TRUE(unpoisoned.IsNull());
+ EXPECT_TRUE(unpoisoned.IsValid());
+ EXPECT_TRUE(unpoisoned.Ptr() == nullptr);
+ EXPECT_TRUE(unpoisoned == nullptr);
+ EXPECT_TRUE(unpoisoned == unpoisoned);
+ EXPECT_FALSE(unpoisoned != unpoisoned);
+ EXPECT_FALSE(unpoisoned != nullptr);
- unpoisoned = h_X.Get();
- EXPECT_FALSE(unpoisoned.IsNull());
- EXPECT_TRUE(unpoisoned == h_X.Get());
- EXPECT_OBJ_PTR_EQ(unpoisoned, h_X.Get());
+ unpoisoned = h_X.Get();
+ EXPECT_FALSE(unpoisoned.IsNull());
+ EXPECT_TRUE(unpoisoned == h_X.Get());
+ EXPECT_OBJ_PTR_EQ(unpoisoned, h_X.Get());
+ }
}
} // namespace mirror
diff --git a/runtime/mirror/string-inl.h b/runtime/mirror/string-inl.h
index c2407d7..57b20a1 100644
--- a/runtime/mirror/string-inl.h
+++ b/runtime/mirror/string-inl.h
@@ -36,7 +36,7 @@
namespace mirror {
inline uint32_t String::ClassSize(PointerSize pointer_size) {
- uint32_t vtable_entries = Object::kVTableLength + 57;
+ uint32_t vtable_entries = Object::kVTableLength + 56;
return Class::ComputeClassSize(true, vtable_entries, 0, 0, 0, 1, 2, pointer_size);
}
@@ -311,9 +311,7 @@
inline bool String::AllASCII(const MemoryType* chars, const int length) {
static_assert(std::is_unsigned<MemoryType>::value, "Expecting unsigned MemoryType");
for (int i = 0; i < length; ++i) {
- // Valid ASCII characters are in range 1..0x7f. Zero is not considered ASCII
- // because it would complicate the detection of ASCII strings in Modified-UTF8.
- if ((chars[i] - 1u) >= 0x7fu) {
+ if (!IsASCII(chars[i])) {
return false;
}
}
diff --git a/runtime/mirror/string.cc b/runtime/mirror/string.cc
index 0ab0bd6..884b88a 100644
--- a/runtime/mirror/string.cc
+++ b/runtime/mirror/string.cc
@@ -79,14 +79,55 @@
}
}
-void String::SetCharAt(int32_t index, uint16_t c) {
- DCHECK((index >= 0) && (index < GetLength()));
- if (IsCompressed()) {
- // TODO: Handle the case where String is compressed and c is non-ASCII
- GetValueCompressed()[index] = static_cast<uint8_t>(c);
- } else {
- GetValue()[index] = c;
+inline bool String::AllASCIIExcept(const uint16_t* chars, int32_t length, uint16_t non_ascii) {
+ DCHECK(!IsASCII(non_ascii));
+ for (int32_t i = 0; i < length; ++i) {
+ if (!IsASCII(chars[i]) && chars[i] != non_ascii) {
+ return false;
+ }
}
+ return true;
+}
+
+ObjPtr<String> String::DoReplace(Thread* self, uint16_t old_c, uint16_t new_c) {
+ DCHECK(IsCompressed() ? ContainsElement(ArrayRef<uint8_t>(value_compressed_, GetLength()), old_c)
+ : ContainsElement(ArrayRef<uint16_t>(value_, GetLength()), old_c));
+ int32_t length = GetLength();
+ bool compressible =
+ kUseStringCompression &&
+ IsASCII(new_c) &&
+ (IsCompressed() || (!IsASCII(old_c) && AllASCIIExcept(value_, length, old_c)));
+ gc::AllocatorType allocator_type = Runtime::Current()->GetHeap()->GetCurrentAllocator();
+ const int32_t length_with_flag = String::GetFlaggedCount(GetLength(), compressible);
+ SetStringCountVisitor visitor(length_with_flag);
+ ObjPtr<String> string = Alloc<true>(self, length_with_flag, allocator_type, visitor);
+ if (UNLIKELY(string == nullptr)) {
+ return nullptr;
+ }
+ if (compressible) {
+ auto replace = [old_c, new_c](uint16_t c) {
+ return dchecked_integral_cast<uint8_t>((old_c != c) ? c : new_c);
+ };
+ uint8_t* out = string->value_compressed_;
+ if (LIKELY(IsCompressed())) { // LIKELY(compressible == IsCompressed())
+ std::transform(value_compressed_, value_compressed_ + length, out, replace);
+ } else {
+ std::transform(value_, value_ + length, out, replace);
+ }
+ DCHECK(kUseStringCompression && AllASCII(out, length));
+ } else {
+ auto replace = [old_c, new_c](uint16_t c) {
+ return (old_c != c) ? c : new_c;
+ };
+ uint16_t* out = string->value_;
+ if (UNLIKELY(IsCompressed())) { // LIKELY(compressible == IsCompressed())
+ std::transform(value_compressed_, value_compressed_ + length, out, replace);
+ } else {
+ std::transform(value_, value_ + length, out, replace);
+ }
+ DCHECK(!kUseStringCompression || !AllASCII(out, length));
+ }
+ return string;
}
String* String::AllocFromStrings(Thread* self, Handle<String> string, Handle<String> string2) {
diff --git a/runtime/mirror/string.h b/runtime/mirror/string.h
index 38f6dd4..35ce98e 100644
--- a/runtime/mirror/string.h
+++ b/runtime/mirror/string.h
@@ -94,7 +94,10 @@
uint16_t CharAt(int32_t index) REQUIRES_SHARED(Locks::mutator_lock_);
- void SetCharAt(int32_t index, uint16_t c) REQUIRES_SHARED(Locks::mutator_lock_);
+ // Create a new string where all occurences of `old_c` are replaced with `new_c`.
+ // String.doReplace(char, char) is called from String.replace(char, char) when there is a match.
+ ObjPtr<String> DoReplace(Thread* self, uint16_t old_c, uint16_t new_c)
+ REQUIRES_SHARED(Locks::mutator_lock_);
ObjPtr<String> Intern() REQUIRES_SHARED(Locks::mutator_lock_);
@@ -229,6 +232,14 @@
REQUIRES_SHARED(Locks::mutator_lock_);
private:
+ static constexpr bool IsASCII(uint16_t c) {
+ // Valid ASCII characters are in range 1..0x7f. Zero is not considered ASCII
+ // because it would complicate the detection of ASCII strings in Modified-UTF8.
+ return (c - 1u) < 0x7fu;
+ }
+
+ static bool AllASCIIExcept(const uint16_t* chars, int32_t length, uint16_t non_ascii);
+
void SetHashCode(int32_t new_hash_code) REQUIRES_SHARED(Locks::mutator_lock_) {
// Hash code is invariant so use non-transactional mode. Also disable check as we may run inside
// a transaction.
diff --git a/runtime/modifiers.h b/runtime/modifiers.h
index ae6b31d..461f870 100644
--- a/runtime/modifiers.h
+++ b/runtime/modifiers.h
@@ -45,6 +45,9 @@
static constexpr uint32_t kAccConstructor = 0x00010000; // method (dex only) <(cl)init>
static constexpr uint32_t kAccDeclaredSynchronized = 0x00020000; // method (dex only)
static constexpr uint32_t kAccClassIsProxy = 0x00040000; // class (dex only)
+// Set to indicate that the ArtMethod is obsolete and has a different DexCache + DexFile from its
+// declaring class. This flag may only be applied to methods.
+static constexpr uint32_t kAccObsoleteMethod = 0x00040000; // method (runtime)
// Used by a method to denote that its execution does not need to go through slow path interpreter.
static constexpr uint32_t kAccSkipAccessChecks = 0x00080000; // method (dex only)
// Used by a class to denote that the verifier has attempted to check it at least once.
@@ -67,10 +70,6 @@
// Set by the verifier for a method that could not be verified to follow structured locking.
static constexpr uint32_t kAccMustCountLocks = 0x02000000; // method (runtime)
-// Set to indicate that the ArtMethod is obsolete and has a different DexCache from its declaring
-// class.
-// TODO Might want to re-arrange some of these so that we can have obsolete + intrinsic methods.
-static constexpr uint32_t kAccObsoleteMethod = 0x04000000; // method (runtime)
// Set by the class linker for a method that has only one implementation for a
// virtual call.
diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc
index 0d24587..f6a73a8 100644
--- a/runtime/native/dalvik_system_VMDebug.cc
+++ b/runtime/native/dalvik_system_VMDebug.cc
@@ -537,14 +537,14 @@
NATIVE_METHOD(VMDebug, getAllocCount, "(I)I"),
NATIVE_METHOD(VMDebug, getHeapSpaceStats, "([J)V"),
NATIVE_METHOD(VMDebug, getInstructionCount, "([I)V"),
- NATIVE_METHOD(VMDebug, getLoadedClassCount, "!()I"),
+ FAST_NATIVE_METHOD(VMDebug, getLoadedClassCount, "()I"),
NATIVE_METHOD(VMDebug, getVmFeatureList, "()[Ljava/lang/String;"),
NATIVE_METHOD(VMDebug, infopoint, "(I)V"),
- NATIVE_METHOD(VMDebug, isDebuggerConnected, "!()Z"),
- NATIVE_METHOD(VMDebug, isDebuggingEnabled, "!()Z"),
+ FAST_NATIVE_METHOD(VMDebug, isDebuggerConnected, "()Z"),
+ FAST_NATIVE_METHOD(VMDebug, isDebuggingEnabled, "()Z"),
NATIVE_METHOD(VMDebug, getMethodTracingMode, "()I"),
- NATIVE_METHOD(VMDebug, lastDebuggerActivity, "!()J"),
- NATIVE_METHOD(VMDebug, printLoadedClasses, "!(I)V"),
+ FAST_NATIVE_METHOD(VMDebug, lastDebuggerActivity, "()J"),
+ FAST_NATIVE_METHOD(VMDebug, printLoadedClasses, "(I)V"),
NATIVE_METHOD(VMDebug, resetAllocCount, "(I)V"),
NATIVE_METHOD(VMDebug, resetInstructionCount, "()V"),
NATIVE_METHOD(VMDebug, startAllocCounting, "()V"),
@@ -557,7 +557,7 @@
NATIVE_METHOD(VMDebug, stopEmulatorTracing, "()V"),
NATIVE_METHOD(VMDebug, stopInstructionCounting, "()V"),
NATIVE_METHOD(VMDebug, stopMethodTracing, "()V"),
- NATIVE_METHOD(VMDebug, threadCpuTimeNanos, "!()J"),
+ FAST_NATIVE_METHOD(VMDebug, threadCpuTimeNanos, "()J"),
NATIVE_METHOD(VMDebug, getRuntimeStatInternal, "(I)Ljava/lang/String;"),
NATIVE_METHOD(VMDebug, getRuntimeStatsInternal, "()[Ljava/lang/String;"),
NATIVE_METHOD(VMDebug, attachAgent, "(Ljava/lang/String;)V"),
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index 6bfccdc..efc42fd 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -642,7 +642,7 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(VMRuntime, addressOf, "!(Ljava/lang/Object;)J"),
+ FAST_NATIVE_METHOD(VMRuntime, addressOf, "(Ljava/lang/Object;)J"),
NATIVE_METHOD(VMRuntime, bootClassPath, "()Ljava/lang/String;"),
NATIVE_METHOD(VMRuntime, clampGrowthLimit, "()V"),
NATIVE_METHOD(VMRuntime, classPath, "()Ljava/lang/String;"),
@@ -650,11 +650,11 @@
NATIVE_METHOD(VMRuntime, concurrentGC, "()V"),
NATIVE_METHOD(VMRuntime, disableJitCompilation, "()V"),
NATIVE_METHOD(VMRuntime, getTargetHeapUtilization, "()F"),
- NATIVE_METHOD(VMRuntime, isDebuggerActive, "!()Z"),
- NATIVE_METHOD(VMRuntime, isNativeDebuggable, "!()Z"),
+ FAST_NATIVE_METHOD(VMRuntime, isDebuggerActive, "()Z"),
+ FAST_NATIVE_METHOD(VMRuntime, isNativeDebuggable, "()Z"),
NATIVE_METHOD(VMRuntime, nativeSetTargetHeapUtilization, "(F)V"),
- NATIVE_METHOD(VMRuntime, newNonMovableArray, "!(Ljava/lang/Class;I)Ljava/lang/Object;"),
- NATIVE_METHOD(VMRuntime, newUnpaddedArray, "!(Ljava/lang/Class;I)Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(VMRuntime, newNonMovableArray, "(Ljava/lang/Class;I)Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(VMRuntime, newUnpaddedArray, "(Ljava/lang/Class;I)Ljava/lang/Object;"),
NATIVE_METHOD(VMRuntime, properties, "()[Ljava/lang/String;"),
NATIVE_METHOD(VMRuntime, setTargetSdkVersionNative, "(I)V"),
NATIVE_METHOD(VMRuntime, registerNativeAllocation, "(I)V"),
@@ -671,8 +671,8 @@
NATIVE_METHOD(VMRuntime, vmVersion, "()Ljava/lang/String;"),
NATIVE_METHOD(VMRuntime, vmLibrary, "()Ljava/lang/String;"),
NATIVE_METHOD(VMRuntime, vmInstructionSet, "()Ljava/lang/String;"),
- NATIVE_METHOD(VMRuntime, is64Bit, "!()Z"),
- NATIVE_METHOD(VMRuntime, isCheckJniEnabled, "!()Z"),
+ FAST_NATIVE_METHOD(VMRuntime, is64Bit, "()Z"),
+ FAST_NATIVE_METHOD(VMRuntime, isCheckJniEnabled, "()Z"),
NATIVE_METHOD(VMRuntime, preloadDexCaches, "()V"),
NATIVE_METHOD(VMRuntime, registerAppInfo,
"(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V"),
diff --git a/runtime/native/dalvik_system_VMStack.cc b/runtime/native/dalvik_system_VMStack.cc
index be6f7f2..0dfafa4 100644
--- a/runtime/native/dalvik_system_VMStack.cc
+++ b/runtime/native/dalvik_system_VMStack.cc
@@ -139,11 +139,11 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(VMStack, fillStackTraceElements, "!(Ljava/lang/Thread;[Ljava/lang/StackTraceElement;)I"),
- NATIVE_METHOD(VMStack, getCallingClassLoader, "!()Ljava/lang/ClassLoader;"),
- NATIVE_METHOD(VMStack, getClosestUserClassLoader, "!()Ljava/lang/ClassLoader;"),
- NATIVE_METHOD(VMStack, getStackClass2, "!()Ljava/lang/Class;"),
- NATIVE_METHOD(VMStack, getThreadStackTrace, "!(Ljava/lang/Thread;)[Ljava/lang/StackTraceElement;"),
+ FAST_NATIVE_METHOD(VMStack, fillStackTraceElements, "(Ljava/lang/Thread;[Ljava/lang/StackTraceElement;)I"),
+ FAST_NATIVE_METHOD(VMStack, getCallingClassLoader, "()Ljava/lang/ClassLoader;"),
+ FAST_NATIVE_METHOD(VMStack, getClosestUserClassLoader, "()Ljava/lang/ClassLoader;"),
+ FAST_NATIVE_METHOD(VMStack, getStackClass2, "()Ljava/lang/Class;"),
+ FAST_NATIVE_METHOD(VMStack, getThreadStackTrace, "(Ljava/lang/Thread;)[Ljava/lang/StackTraceElement;"),
};
void register_dalvik_system_VMStack(JNIEnv* env) {
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index 256787b..c8431c0 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -713,36 +713,36 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(Class, classForName,
- "!(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"),
- NATIVE_METHOD(Class, getDeclaredAnnotation,
- "!(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;"),
- NATIVE_METHOD(Class, getDeclaredAnnotations, "!()[Ljava/lang/annotation/Annotation;"),
- NATIVE_METHOD(Class, getDeclaredClasses, "!()[Ljava/lang/Class;"),
- NATIVE_METHOD(Class, getDeclaredConstructorInternal,
- "!([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"),
- NATIVE_METHOD(Class, getDeclaredConstructorsInternal, "!(Z)[Ljava/lang/reflect/Constructor;"),
- NATIVE_METHOD(Class, getDeclaredField, "!(Ljava/lang/String;)Ljava/lang/reflect/Field;"),
- NATIVE_METHOD(Class, getPublicFieldRecursive, "!(Ljava/lang/String;)Ljava/lang/reflect/Field;"),
- NATIVE_METHOD(Class, getDeclaredFields, "!()[Ljava/lang/reflect/Field;"),
- NATIVE_METHOD(Class, getDeclaredFieldsUnchecked, "!(Z)[Ljava/lang/reflect/Field;"),
- NATIVE_METHOD(Class, getDeclaredMethodInternal,
- "!(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"),
- NATIVE_METHOD(Class, getDeclaredMethodsUnchecked,
- "!(Z)[Ljava/lang/reflect/Method;"),
- NATIVE_METHOD(Class, getDeclaringClass, "!()Ljava/lang/Class;"),
- NATIVE_METHOD(Class, getEnclosingClass, "!()Ljava/lang/Class;"),
- NATIVE_METHOD(Class, getEnclosingConstructorNative, "!()Ljava/lang/reflect/Constructor;"),
- NATIVE_METHOD(Class, getEnclosingMethodNative, "!()Ljava/lang/reflect/Method;"),
- NATIVE_METHOD(Class, getInnerClassFlags, "!(I)I"),
- NATIVE_METHOD(Class, getInnerClassName, "!()Ljava/lang/String;"),
- NATIVE_METHOD(Class, getNameNative, "!()Ljava/lang/String;"),
- NATIVE_METHOD(Class, getProxyInterfaces, "!()[Ljava/lang/Class;"),
- NATIVE_METHOD(Class, getPublicDeclaredFields, "!()[Ljava/lang/reflect/Field;"),
- NATIVE_METHOD(Class, getSignatureAnnotation, "!()[Ljava/lang/String;"),
- NATIVE_METHOD(Class, isAnonymousClass, "!()Z"),
- NATIVE_METHOD(Class, isDeclaredAnnotationPresent, "!(Ljava/lang/Class;)Z"),
- NATIVE_METHOD(Class, newInstance, "!()Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(Class, classForName,
+ "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"),
+ FAST_NATIVE_METHOD(Class, getDeclaredAnnotation,
+ "(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;"),
+ FAST_NATIVE_METHOD(Class, getDeclaredAnnotations, "()[Ljava/lang/annotation/Annotation;"),
+ FAST_NATIVE_METHOD(Class, getDeclaredClasses, "()[Ljava/lang/Class;"),
+ FAST_NATIVE_METHOD(Class, getDeclaredConstructorInternal,
+ "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"),
+ FAST_NATIVE_METHOD(Class, getDeclaredConstructorsInternal, "(Z)[Ljava/lang/reflect/Constructor;"),
+ FAST_NATIVE_METHOD(Class, getDeclaredField, "(Ljava/lang/String;)Ljava/lang/reflect/Field;"),
+ FAST_NATIVE_METHOD(Class, getPublicFieldRecursive, "(Ljava/lang/String;)Ljava/lang/reflect/Field;"),
+ FAST_NATIVE_METHOD(Class, getDeclaredFields, "()[Ljava/lang/reflect/Field;"),
+ FAST_NATIVE_METHOD(Class, getDeclaredFieldsUnchecked, "(Z)[Ljava/lang/reflect/Field;"),
+ FAST_NATIVE_METHOD(Class, getDeclaredMethodInternal,
+ "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"),
+ FAST_NATIVE_METHOD(Class, getDeclaredMethodsUnchecked,
+ "(Z)[Ljava/lang/reflect/Method;"),
+ FAST_NATIVE_METHOD(Class, getDeclaringClass, "()Ljava/lang/Class;"),
+ FAST_NATIVE_METHOD(Class, getEnclosingClass, "()Ljava/lang/Class;"),
+ FAST_NATIVE_METHOD(Class, getEnclosingConstructorNative, "()Ljava/lang/reflect/Constructor;"),
+ FAST_NATIVE_METHOD(Class, getEnclosingMethodNative, "()Ljava/lang/reflect/Method;"),
+ FAST_NATIVE_METHOD(Class, getInnerClassFlags, "(I)I"),
+ FAST_NATIVE_METHOD(Class, getInnerClassName, "()Ljava/lang/String;"),
+ FAST_NATIVE_METHOD(Class, getNameNative, "()Ljava/lang/String;"),
+ FAST_NATIVE_METHOD(Class, getProxyInterfaces, "()[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"),
+ FAST_NATIVE_METHOD(Class, isDeclaredAnnotationPresent, "(Ljava/lang/Class;)Z"),
+ FAST_NATIVE_METHOD(Class, newInstance, "()Ljava/lang/Object;"),
};
void register_java_lang_Class(JNIEnv* env) {
diff --git a/runtime/native/java_lang_DexCache.cc b/runtime/native/java_lang_DexCache.cc
index b1ed74a..8fda4df 100644
--- a/runtime/native/java_lang_DexCache.cc
+++ b/runtime/native/java_lang_DexCache.cc
@@ -53,7 +53,7 @@
static jobject DexCache_getResolvedType(JNIEnv* env, jobject javaDexCache, jint type_index) {
ScopedFastNativeObjectAccess soa(env);
ObjPtr<mirror::DexCache> dex_cache = soa.Decode<mirror::DexCache>(javaDexCache);
- CHECK_LT(static_cast<size_t>(type_index), dex_cache->NumResolvedTypes());
+ CHECK_LT(static_cast<size_t>(type_index), dex_cache->GetDexFile()->NumTypeIds());
return soa.AddLocalReference<jobject>(dex_cache->GetResolvedType(dex::TypeIndex(type_index)));
}
@@ -95,11 +95,11 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(DexCache, getDexNative, "!()Lcom/android/dex/Dex;"),
- NATIVE_METHOD(DexCache, getResolvedType, "!(I)Ljava/lang/Class;"),
- NATIVE_METHOD(DexCache, getResolvedString, "!(I)Ljava/lang/String;"),
- NATIVE_METHOD(DexCache, setResolvedType, "!(ILjava/lang/Class;)V"),
- NATIVE_METHOD(DexCache, setResolvedString, "!(ILjava/lang/String;)V"),
+ FAST_NATIVE_METHOD(DexCache, getDexNative, "()Lcom/android/dex/Dex;"),
+ FAST_NATIVE_METHOD(DexCache, getResolvedType, "(I)Ljava/lang/Class;"),
+ FAST_NATIVE_METHOD(DexCache, getResolvedString, "(I)Ljava/lang/String;"),
+ FAST_NATIVE_METHOD(DexCache, setResolvedType, "(ILjava/lang/Class;)V"),
+ FAST_NATIVE_METHOD(DexCache, setResolvedString, "(ILjava/lang/String;)V"),
};
void register_java_lang_DexCache(JNIEnv* env) {
diff --git a/runtime/native/java_lang_Object.cc b/runtime/native/java_lang_Object.cc
index 6493865..6989244 100644
--- a/runtime/native/java_lang_Object.cc
+++ b/runtime/native/java_lang_Object.cc
@@ -20,7 +20,6 @@
#include "mirror/object-inl.h"
#include "scoped_fast_native_object_access-inl.h"
-
namespace art {
static jobject Object_internalClone(JNIEnv* env, jobject java_this) {
@@ -50,11 +49,11 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(Object, internalClone, "!()Ljava/lang/Object;"),
- NATIVE_METHOD(Object, notify, "!()V"),
- NATIVE_METHOD(Object, notifyAll, "!()V"),
- OVERLOADED_NATIVE_METHOD(Object, wait, "!()V", wait),
- OVERLOADED_NATIVE_METHOD(Object, wait, "!(JI)V", waitJI),
+ FAST_NATIVE_METHOD(Object, internalClone, "()Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(Object, notify, "()V"),
+ FAST_NATIVE_METHOD(Object, notifyAll, "()V"),
+ OVERLOADED_FAST_NATIVE_METHOD(Object, wait, "()V", wait),
+ OVERLOADED_FAST_NATIVE_METHOD(Object, wait, "(JI)V", waitJI),
};
void register_java_lang_Object(JNIEnv* env) {
diff --git a/runtime/native/java_lang_String.cc b/runtime/native/java_lang_String.cc
index f1d6ff5..2e561ff 100644
--- a/runtime/native/java_lang_String.cc
+++ b/runtime/native/java_lang_String.cc
@@ -99,9 +99,11 @@
return soa.AddLocalReference<jstring>(result);
}
-static void String_setCharAt(JNIEnv* env, jobject java_this, jint index, jchar c) {
+static jstring String_doReplace(JNIEnv* env, jobject java_this, jchar old_c, jchar new_c) {
ScopedFastNativeObjectAccess soa(env);
- soa.Decode<mirror::String>(java_this)->SetCharAt(index, c);
+ ObjPtr<mirror::String> result =
+ soa.Decode<mirror::String>(java_this)->DoReplace(soa.Self(), old_c, new_c);
+ return soa.AddLocalReference<jstring>(result);
}
static jcharArray String_toCharArray(JNIEnv* env, jobject java_this) {
@@ -111,15 +113,15 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(String, charAt, "!(I)C"),
- NATIVE_METHOD(String, compareTo, "!(Ljava/lang/String;)I"),
- NATIVE_METHOD(String, concat, "!(Ljava/lang/String;)Ljava/lang/String;"),
- NATIVE_METHOD(String, fastIndexOf, "!(II)I"),
- NATIVE_METHOD(String, fastSubstring, "!(II)Ljava/lang/String;"),
- NATIVE_METHOD(String, getCharsNoCheck, "!(II[CI)V"),
- NATIVE_METHOD(String, intern, "!()Ljava/lang/String;"),
- NATIVE_METHOD(String, setCharAt, "!(IC)V"),
- NATIVE_METHOD(String, toCharArray, "!()[C"),
+ FAST_NATIVE_METHOD(String, charAt, "(I)C"),
+ FAST_NATIVE_METHOD(String, compareTo, "(Ljava/lang/String;)I"),
+ FAST_NATIVE_METHOD(String, concat, "(Ljava/lang/String;)Ljava/lang/String;"),
+ FAST_NATIVE_METHOD(String, doReplace, "(CC)Ljava/lang/String;"),
+ FAST_NATIVE_METHOD(String, fastIndexOf, "(II)I"),
+ FAST_NATIVE_METHOD(String, fastSubstring, "(II)Ljava/lang/String;"),
+ FAST_NATIVE_METHOD(String, getCharsNoCheck, "(II[CI)V"),
+ FAST_NATIVE_METHOD(String, intern, "()Ljava/lang/String;"),
+ FAST_NATIVE_METHOD(String, toCharArray, "()[C"),
};
void register_java_lang_String(JNIEnv* env) {
diff --git a/runtime/native/java_lang_StringFactory.cc b/runtime/native/java_lang_StringFactory.cc
index e0738a4..ec3c7c2 100644
--- a/runtime/native/java_lang_StringFactory.cc
+++ b/runtime/native/java_lang_StringFactory.cc
@@ -87,9 +87,9 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(StringFactory, newStringFromBytes, "!([BIII)Ljava/lang/String;"),
- NATIVE_METHOD(StringFactory, newStringFromChars, "!(II[C)Ljava/lang/String;"),
- NATIVE_METHOD(StringFactory, newStringFromString, "!(Ljava/lang/String;)Ljava/lang/String;"),
+ FAST_NATIVE_METHOD(StringFactory, newStringFromBytes, "([BIII)Ljava/lang/String;"),
+ FAST_NATIVE_METHOD(StringFactory, newStringFromChars, "(II[C)Ljava/lang/String;"),
+ FAST_NATIVE_METHOD(StringFactory, newStringFromString, "(Ljava/lang/String;)Ljava/lang/String;"),
};
void register_java_lang_StringFactory(JNIEnv* env) {
diff --git a/runtime/native/java_lang_System.cc b/runtime/native/java_lang_System.cc
index 7f8da80..d7c9cd0 100644
--- a/runtime/native/java_lang_System.cc
+++ b/runtime/native/java_lang_System.cc
@@ -237,16 +237,16 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(System, arraycopy, "!(Ljava/lang/Object;ILjava/lang/Object;II)V"),
- NATIVE_METHOD(System, arraycopyCharUnchecked, "!([CI[CII)V"),
- NATIVE_METHOD(System, arraycopyByteUnchecked, "!([BI[BII)V"),
- NATIVE_METHOD(System, arraycopyShortUnchecked, "!([SI[SII)V"),
- NATIVE_METHOD(System, arraycopyIntUnchecked, "!([II[III)V"),
- NATIVE_METHOD(System, arraycopyLongUnchecked, "!([JI[JII)V"),
- NATIVE_METHOD(System, arraycopyFloatUnchecked, "!([FI[FII)V"),
- NATIVE_METHOD(System, arraycopyDoubleUnchecked, "!([DI[DII)V"),
- NATIVE_METHOD(System, arraycopyBooleanUnchecked, "!([ZI[ZII)V"),
- NATIVE_METHOD(System, identityHashCode, "!(Ljava/lang/Object;)I"),
+ FAST_NATIVE_METHOD(System, arraycopy, "(Ljava/lang/Object;ILjava/lang/Object;II)V"),
+ FAST_NATIVE_METHOD(System, arraycopyCharUnchecked, "([CI[CII)V"),
+ FAST_NATIVE_METHOD(System, arraycopyByteUnchecked, "([BI[BII)V"),
+ FAST_NATIVE_METHOD(System, arraycopyShortUnchecked, "([SI[SII)V"),
+ FAST_NATIVE_METHOD(System, arraycopyIntUnchecked, "([II[III)V"),
+ FAST_NATIVE_METHOD(System, arraycopyLongUnchecked, "([JI[JII)V"),
+ FAST_NATIVE_METHOD(System, arraycopyFloatUnchecked, "([FI[FII)V"),
+ FAST_NATIVE_METHOD(System, arraycopyDoubleUnchecked, "([DI[DII)V"),
+ FAST_NATIVE_METHOD(System, arraycopyBooleanUnchecked, "([ZI[ZII)V"),
+ FAST_NATIVE_METHOD(System, identityHashCode, "(Ljava/lang/Object;)I"),
};
void register_java_lang_System(JNIEnv* env) {
diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc
index 195091f..346bd30 100644
--- a/runtime/native/java_lang_Thread.cc
+++ b/runtime/native/java_lang_Thread.cc
@@ -187,16 +187,16 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(Thread, currentThread, "!()Ljava/lang/Thread;"),
- NATIVE_METHOD(Thread, interrupted, "!()Z"),
- NATIVE_METHOD(Thread, isInterrupted, "!()Z"),
+ FAST_NATIVE_METHOD(Thread, currentThread, "()Ljava/lang/Thread;"),
+ FAST_NATIVE_METHOD(Thread, interrupted, "()Z"),
+ FAST_NATIVE_METHOD(Thread, isInterrupted, "()Z"),
NATIVE_METHOD(Thread, nativeCreate, "(Ljava/lang/Thread;JZ)V"),
NATIVE_METHOD(Thread, nativeGetStatus, "(Z)I"),
NATIVE_METHOD(Thread, nativeHoldsLock, "(Ljava/lang/Object;)Z"),
- NATIVE_METHOD(Thread, nativeInterrupt, "!()V"),
+ FAST_NATIVE_METHOD(Thread, nativeInterrupt, "()V"),
NATIVE_METHOD(Thread, nativeSetName, "(Ljava/lang/String;)V"),
NATIVE_METHOD(Thread, nativeSetPriority, "(I)V"),
- NATIVE_METHOD(Thread, sleep, "!(Ljava/lang/Object;JI)V"),
+ FAST_NATIVE_METHOD(Thread, sleep, "(Ljava/lang/Object;JI)V"),
NATIVE_METHOD(Thread, yield, "()V"),
};
diff --git a/runtime/native/java_lang_Throwable.cc b/runtime/native/java_lang_Throwable.cc
index ff3e044..654b8a8 100644
--- a/runtime/native/java_lang_Throwable.cc
+++ b/runtime/native/java_lang_Throwable.cc
@@ -36,8 +36,8 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(Throwable, nativeFillInStackTrace, "!()Ljava/lang/Object;"),
- NATIVE_METHOD(Throwable, nativeGetStackTrace, "!(Ljava/lang/Object;)[Ljava/lang/StackTraceElement;"),
+ FAST_NATIVE_METHOD(Throwable, nativeFillInStackTrace, "()Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(Throwable, nativeGetStackTrace, "(Ljava/lang/Object;)[Ljava/lang/StackTraceElement;"),
};
void register_java_lang_Throwable(JNIEnv* env) {
diff --git a/runtime/native/java_lang_VMClassLoader.cc b/runtime/native/java_lang_VMClassLoader.cc
index a8fa7db..54ab861 100644
--- a/runtime/native/java_lang_VMClassLoader.cc
+++ b/runtime/native/java_lang_VMClassLoader.cc
@@ -136,7 +136,7 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(VMClassLoader, findLoadedClass, "!(Ljava/lang/ClassLoader;Ljava/lang/String;)Ljava/lang/Class;"),
+ FAST_NATIVE_METHOD(VMClassLoader, findLoadedClass, "(Ljava/lang/ClassLoader;Ljava/lang/String;)Ljava/lang/Class;"),
NATIVE_METHOD(VMClassLoader, getBootClassPathEntries, "()[Ljava/lang/String;"),
};
diff --git a/runtime/native/java_lang_ref_FinalizerReference.cc b/runtime/native/java_lang_ref_FinalizerReference.cc
index ecafd0e..afedc5e 100644
--- a/runtime/native/java_lang_ref_FinalizerReference.cc
+++ b/runtime/native/java_lang_ref_FinalizerReference.cc
@@ -40,8 +40,8 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(FinalizerReference, makeCircularListIfUnenqueued, "!()Z"),
- NATIVE_METHOD(FinalizerReference, getReferent, "!()Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(FinalizerReference, makeCircularListIfUnenqueued, "()Z"),
+ FAST_NATIVE_METHOD(FinalizerReference, getReferent, "()Ljava/lang/Object;"),
};
void register_java_lang_ref_FinalizerReference(JNIEnv* env) {
diff --git a/runtime/native/java_lang_ref_Reference.cc b/runtime/native/java_lang_ref_Reference.cc
index c778068..b1cb2f2 100644
--- a/runtime/native/java_lang_ref_Reference.cc
+++ b/runtime/native/java_lang_ref_Reference.cc
@@ -40,8 +40,8 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(Reference, getReferent, "!()Ljava/lang/Object;"),
- NATIVE_METHOD(Reference, clearReferent, "!()V"),
+ FAST_NATIVE_METHOD(Reference, getReferent, "()Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(Reference, clearReferent, "()V"),
};
void register_java_lang_ref_Reference(JNIEnv* env) {
diff --git a/runtime/native/java_lang_reflect_Array.cc b/runtime/native/java_lang_reflect_Array.cc
index d827f81..54c2109 100644
--- a/runtime/native/java_lang_reflect_Array.cc
+++ b/runtime/native/java_lang_reflect_Array.cc
@@ -72,8 +72,8 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(Array, createMultiArray, "!(Ljava/lang/Class;[I)Ljava/lang/Object;"),
- NATIVE_METHOD(Array, createObjectArray, "!(Ljava/lang/Class;I)Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(Array, createMultiArray, "(Ljava/lang/Class;[I)Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(Array, createObjectArray, "(Ljava/lang/Class;I)Ljava/lang/Object;"),
};
void register_java_lang_reflect_Array(JNIEnv* env) {
diff --git a/runtime/native/java_lang_reflect_Constructor.cc b/runtime/native/java_lang_reflect_Constructor.cc
index 66a5359..fb78046 100644
--- a/runtime/native/java_lang_reflect_Constructor.cc
+++ b/runtime/native/java_lang_reflect_Constructor.cc
@@ -124,9 +124,9 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(Constructor, getExceptionTypes, "!()[Ljava/lang/Class;"),
- NATIVE_METHOD(Constructor, newInstance0, "!([Ljava/lang/Object;)Ljava/lang/Object;"),
- NATIVE_METHOD(Constructor, newInstanceFromSerialization, "!(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(Constructor, getExceptionTypes, "()[Ljava/lang/Class;"),
+ FAST_NATIVE_METHOD(Constructor, newInstance0, "([Ljava/lang/Object;)Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(Constructor, newInstanceFromSerialization, "(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/Object;"),
};
void register_java_lang_reflect_Constructor(JNIEnv* env) {
diff --git a/runtime/native/java_lang_reflect_Executable.cc b/runtime/native/java_lang_reflect_Executable.cc
index 2a39428..bc23bed 100644
--- a/runtime/native/java_lang_reflect_Executable.cc
+++ b/runtime/native/java_lang_reflect_Executable.cc
@@ -195,14 +195,14 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(Executable, getAnnotationNative,
- "!(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;"),
- NATIVE_METHOD(Executable, getDeclaredAnnotationsNative, "!()[Ljava/lang/annotation/Annotation;"),
- NATIVE_METHOD(Executable, getParameterAnnotationsNative,
- "!()[[Ljava/lang/annotation/Annotation;"),
- NATIVE_METHOD(Executable, getParameters0, "!()[Ljava/lang/reflect/Parameter;"),
- NATIVE_METHOD(Executable, getSignatureAnnotation, "!()[Ljava/lang/String;"),
- NATIVE_METHOD(Executable, isAnnotationPresentNative, "!(Ljava/lang/Class;)Z"),
+ FAST_NATIVE_METHOD(Executable, getAnnotationNative,
+ "(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;"),
+ FAST_NATIVE_METHOD(Executable, getDeclaredAnnotationsNative, "()[Ljava/lang/annotation/Annotation;"),
+ FAST_NATIVE_METHOD(Executable, getParameterAnnotationsNative,
+ "()[[Ljava/lang/annotation/Annotation;"),
+ FAST_NATIVE_METHOD(Executable, getParameters0, "()[Ljava/lang/reflect/Parameter;"),
+ FAST_NATIVE_METHOD(Executable, getSignatureAnnotation, "()[Ljava/lang/String;"),
+ FAST_NATIVE_METHOD(Executable, isAnnotationPresentNative, "(Ljava/lang/Class;)Z"),
};
void register_java_lang_reflect_Executable(JNIEnv* env) {
diff --git a/runtime/native/java_lang_reflect_Field.cc b/runtime/native/java_lang_reflect_Field.cc
index 374eeb5..9cf80a5 100644
--- a/runtime/native/java_lang_reflect_Field.cc
+++ b/runtime/native/java_lang_reflect_Field.cc
@@ -493,30 +493,30 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(Field, get, "!(Ljava/lang/Object;)Ljava/lang/Object;"),
- NATIVE_METHOD(Field, getBoolean, "!(Ljava/lang/Object;)Z"),
- NATIVE_METHOD(Field, getByte, "!(Ljava/lang/Object;)B"),
- NATIVE_METHOD(Field, getChar, "!(Ljava/lang/Object;)C"),
- NATIVE_METHOD(Field, getAnnotationNative,
- "!(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;"),
- NATIVE_METHOD(Field, getArtField, "!()J"),
- NATIVE_METHOD(Field, getDeclaredAnnotations, "!()[Ljava/lang/annotation/Annotation;"),
- NATIVE_METHOD(Field, getSignatureAnnotation, "!()[Ljava/lang/String;"),
- NATIVE_METHOD(Field, getDouble, "!(Ljava/lang/Object;)D"),
- NATIVE_METHOD(Field, getFloat, "!(Ljava/lang/Object;)F"),
- NATIVE_METHOD(Field, getInt, "!(Ljava/lang/Object;)I"),
- NATIVE_METHOD(Field, getLong, "!(Ljava/lang/Object;)J"),
- NATIVE_METHOD(Field, getShort, "!(Ljava/lang/Object;)S"),
- NATIVE_METHOD(Field, isAnnotationPresentNative, "!(Ljava/lang/Class;)Z"),
- NATIVE_METHOD(Field, set, "!(Ljava/lang/Object;Ljava/lang/Object;)V"),
- NATIVE_METHOD(Field, setBoolean, "!(Ljava/lang/Object;Z)V"),
- NATIVE_METHOD(Field, setByte, "!(Ljava/lang/Object;B)V"),
- NATIVE_METHOD(Field, setChar, "!(Ljava/lang/Object;C)V"),
- NATIVE_METHOD(Field, setDouble, "!(Ljava/lang/Object;D)V"),
- NATIVE_METHOD(Field, setFloat, "!(Ljava/lang/Object;F)V"),
- NATIVE_METHOD(Field, setInt, "!(Ljava/lang/Object;I)V"),
- NATIVE_METHOD(Field, setLong, "!(Ljava/lang/Object;J)V"),
- NATIVE_METHOD(Field, setShort, "!(Ljava/lang/Object;S)V"),
+ FAST_NATIVE_METHOD(Field, get, "(Ljava/lang/Object;)Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(Field, getBoolean, "(Ljava/lang/Object;)Z"),
+ FAST_NATIVE_METHOD(Field, getByte, "(Ljava/lang/Object;)B"),
+ FAST_NATIVE_METHOD(Field, getChar, "(Ljava/lang/Object;)C"),
+ FAST_NATIVE_METHOD(Field, getAnnotationNative,
+ "(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;"),
+ FAST_NATIVE_METHOD(Field, getArtField, "()J"),
+ FAST_NATIVE_METHOD(Field, getDeclaredAnnotations, "()[Ljava/lang/annotation/Annotation;"),
+ FAST_NATIVE_METHOD(Field, getSignatureAnnotation, "()[Ljava/lang/String;"),
+ FAST_NATIVE_METHOD(Field, getDouble, "(Ljava/lang/Object;)D"),
+ FAST_NATIVE_METHOD(Field, getFloat, "(Ljava/lang/Object;)F"),
+ FAST_NATIVE_METHOD(Field, getInt, "(Ljava/lang/Object;)I"),
+ FAST_NATIVE_METHOD(Field, getLong, "(Ljava/lang/Object;)J"),
+ FAST_NATIVE_METHOD(Field, getShort, "(Ljava/lang/Object;)S"),
+ FAST_NATIVE_METHOD(Field, isAnnotationPresentNative, "(Ljava/lang/Class;)Z"),
+ FAST_NATIVE_METHOD(Field, set, "(Ljava/lang/Object;Ljava/lang/Object;)V"),
+ FAST_NATIVE_METHOD(Field, setBoolean, "(Ljava/lang/Object;Z)V"),
+ FAST_NATIVE_METHOD(Field, setByte, "(Ljava/lang/Object;B)V"),
+ FAST_NATIVE_METHOD(Field, setChar, "(Ljava/lang/Object;C)V"),
+ FAST_NATIVE_METHOD(Field, setDouble, "(Ljava/lang/Object;D)V"),
+ FAST_NATIVE_METHOD(Field, setFloat, "(Ljava/lang/Object;F)V"),
+ FAST_NATIVE_METHOD(Field, setInt, "(Ljava/lang/Object;I)V"),
+ FAST_NATIVE_METHOD(Field, setLong, "(Ljava/lang/Object;J)V"),
+ FAST_NATIVE_METHOD(Field, setShort, "(Ljava/lang/Object;S)V"),
};
void register_java_lang_reflect_Field(JNIEnv* env) {
diff --git a/runtime/native/java_lang_reflect_Method.cc b/runtime/native/java_lang_reflect_Method.cc
index a6589bc..6e5e3d9 100644
--- a/runtime/native/java_lang_reflect_Method.cc
+++ b/runtime/native/java_lang_reflect_Method.cc
@@ -84,9 +84,9 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(Method, getDefaultValue, "!()Ljava/lang/Object;"),
- NATIVE_METHOD(Method, getExceptionTypes, "!()[Ljava/lang/Class;"),
- NATIVE_METHOD(Method, invoke, "!(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(Method, getDefaultValue, "()Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(Method, getExceptionTypes, "()[Ljava/lang/Class;"),
+ FAST_NATIVE_METHOD(Method, invoke, "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
};
void register_java_lang_reflect_Method(JNIEnv* env) {
diff --git a/runtime/native/java_lang_reflect_Parameter.cc b/runtime/native/java_lang_reflect_Parameter.cc
index 0bb9e38..37aa16c 100644
--- a/runtime/native/java_lang_reflect_Parameter.cc
+++ b/runtime/native/java_lang_reflect_Parameter.cc
@@ -63,9 +63,9 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(Parameter,
+ FAST_NATIVE_METHOD(Parameter,
getAnnotationNative,
- "!(Ljava/lang/reflect/Executable;ILjava/lang/Class;)Ljava/lang/annotation/Annotation;"),
+ "(Ljava/lang/reflect/Executable;ILjava/lang/Class;)Ljava/lang/annotation/Annotation;"),
};
void register_java_lang_reflect_Parameter(JNIEnv* env) {
diff --git a/runtime/native/java_lang_reflect_Proxy.cc b/runtime/native/java_lang_reflect_Proxy.cc
index 70cd6aa..0279b5f 100644
--- a/runtime/native/java_lang_reflect_Proxy.cc
+++ b/runtime/native/java_lang_reflect_Proxy.cc
@@ -35,7 +35,7 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(Proxy, generateProxy, "!(Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/ClassLoader;[Ljava/lang/reflect/Method;[[Ljava/lang/Class;)Ljava/lang/Class;"),
+ FAST_NATIVE_METHOD(Proxy, generateProxy, "(Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/ClassLoader;[Ljava/lang/reflect/Method;[[Ljava/lang/Class;)Ljava/lang/Class;"),
};
void register_java_lang_reflect_Proxy(JNIEnv* env) {
diff --git a/runtime/native/libcore_util_CharsetUtils.cc b/runtime/native/libcore_util_CharsetUtils.cc
index e51b6d2..4138ccc 100644
--- a/runtime/native/libcore_util_CharsetUtils.cc
+++ b/runtime/native/libcore_util_CharsetUtils.cc
@@ -249,11 +249,11 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(CharsetUtils, asciiBytesToChars, "!([BII[C)V"),
- NATIVE_METHOD(CharsetUtils, isoLatin1BytesToChars, "!([BII[C)V"),
- NATIVE_METHOD(CharsetUtils, toAsciiBytes, "!(Ljava/lang/String;II)[B"),
- NATIVE_METHOD(CharsetUtils, toIsoLatin1Bytes, "!(Ljava/lang/String;II)[B"),
- NATIVE_METHOD(CharsetUtils, toUtf8Bytes, "!(Ljava/lang/String;II)[B"),
+ FAST_NATIVE_METHOD(CharsetUtils, asciiBytesToChars, "([BII[C)V"),
+ FAST_NATIVE_METHOD(CharsetUtils, isoLatin1BytesToChars, "([BII[C)V"),
+ FAST_NATIVE_METHOD(CharsetUtils, toAsciiBytes, "(Ljava/lang/String;II)[B"),
+ FAST_NATIVE_METHOD(CharsetUtils, toIsoLatin1Bytes, "(Ljava/lang/String;II)[B"),
+ FAST_NATIVE_METHOD(CharsetUtils, toUtf8Bytes, "(Ljava/lang/String;II)[B"),
};
void register_libcore_util_CharsetUtils(JNIEnv* env) {
diff --git a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc
index 5356498..5809708 100644
--- a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc
+++ b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc
@@ -33,7 +33,7 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(DdmServer, nativeSendChunk, "!(I[BII)V"),
+ FAST_NATIVE_METHOD(DdmServer, nativeSendChunk, "(I[BII)V"),
};
void register_org_apache_harmony_dalvik_ddmc_DdmServer(JNIEnv* env) {
diff --git a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
index ca17c26..69ef59e 100644
--- a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
+++ b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
@@ -165,11 +165,11 @@
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(DdmVmInternal, enableRecentAllocations, "(Z)V"),
- NATIVE_METHOD(DdmVmInternal, getRecentAllocations, "!()[B"),
- NATIVE_METHOD(DdmVmInternal, getRecentAllocationStatus, "!()Z"),
+ FAST_NATIVE_METHOD(DdmVmInternal, getRecentAllocations, "()[B"),
+ FAST_NATIVE_METHOD(DdmVmInternal, getRecentAllocationStatus, "()Z"),
NATIVE_METHOD(DdmVmInternal, getStackTraceById, "(I)[Ljava/lang/StackTraceElement;"),
NATIVE_METHOD(DdmVmInternal, getThreadStats, "()[B"),
- NATIVE_METHOD(DdmVmInternal, heapInfoNotify, "!(I)Z"),
+ FAST_NATIVE_METHOD(DdmVmInternal, heapInfoNotify, "(I)Z"),
NATIVE_METHOD(DdmVmInternal, heapSegmentNotify, "(IIZ)Z"),
NATIVE_METHOD(DdmVmInternal, threadNotify, "(Z)V"),
};
diff --git a/runtime/native/scoped_fast_native_object_access-inl.h b/runtime/native/scoped_fast_native_object_access-inl.h
index 1d73813..50a554c 100644
--- a/runtime/native/scoped_fast_native_object_access-inl.h
+++ b/runtime/native/scoped_fast_native_object_access-inl.h
@@ -27,7 +27,7 @@
inline ScopedFastNativeObjectAccess::ScopedFastNativeObjectAccess(JNIEnv* env)
: ScopedObjectAccessAlreadyRunnable(env) {
Locks::mutator_lock_->AssertSharedHeld(Self());
- DCHECK((*Self()->GetManagedStack()->GetTopQuickFrame())->IsFastNative());
+ DCHECK((*Self()->GetManagedStack()->GetTopQuickFrame())->IsAnnotatedWithFastNative());
// Don't work with raw objects in non-runnable states.
DCHECK_EQ(Self()->GetState(), kRunnable);
}
diff --git a/runtime/native/sun_misc_Unsafe.cc b/runtime/native/sun_misc_Unsafe.cc
index 644df07..cc5a41a 100644
--- a/runtime/native/sun_misc_Unsafe.cc
+++ b/runtime/native/sun_misc_Unsafe.cc
@@ -492,69 +492,69 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(Unsafe, compareAndSwapInt, "!(Ljava/lang/Object;JII)Z"),
- NATIVE_METHOD(Unsafe, compareAndSwapLong, "!(Ljava/lang/Object;JJJ)Z"),
- NATIVE_METHOD(Unsafe, compareAndSwapObject, "!(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z"),
- NATIVE_METHOD(Unsafe, getIntVolatile, "!(Ljava/lang/Object;J)I"),
- NATIVE_METHOD(Unsafe, putIntVolatile, "!(Ljava/lang/Object;JI)V"),
- NATIVE_METHOD(Unsafe, getLongVolatile, "!(Ljava/lang/Object;J)J"),
- NATIVE_METHOD(Unsafe, putLongVolatile, "!(Ljava/lang/Object;JJ)V"),
- NATIVE_METHOD(Unsafe, getObjectVolatile, "!(Ljava/lang/Object;J)Ljava/lang/Object;"),
- NATIVE_METHOD(Unsafe, putObjectVolatile, "!(Ljava/lang/Object;JLjava/lang/Object;)V"),
- NATIVE_METHOD(Unsafe, getInt, "!(Ljava/lang/Object;J)I"),
- NATIVE_METHOD(Unsafe, putInt, "!(Ljava/lang/Object;JI)V"),
- NATIVE_METHOD(Unsafe, putOrderedInt, "!(Ljava/lang/Object;JI)V"),
- NATIVE_METHOD(Unsafe, getLong, "!(Ljava/lang/Object;J)J"),
- NATIVE_METHOD(Unsafe, putLong, "!(Ljava/lang/Object;JJ)V"),
- NATIVE_METHOD(Unsafe, putOrderedLong, "!(Ljava/lang/Object;JJ)V"),
- NATIVE_METHOD(Unsafe, getObject, "!(Ljava/lang/Object;J)Ljava/lang/Object;"),
- NATIVE_METHOD(Unsafe, putObject, "!(Ljava/lang/Object;JLjava/lang/Object;)V"),
- NATIVE_METHOD(Unsafe, putOrderedObject, "!(Ljava/lang/Object;JLjava/lang/Object;)V"),
- NATIVE_METHOD(Unsafe, getArrayBaseOffsetForComponentType, "!(Ljava/lang/Class;)I"),
- NATIVE_METHOD(Unsafe, getArrayIndexScaleForComponentType, "!(Ljava/lang/Class;)I"),
- NATIVE_METHOD(Unsafe, addressSize, "!()I"),
- NATIVE_METHOD(Unsafe, pageSize, "!()I"),
- NATIVE_METHOD(Unsafe, allocateMemory, "!(J)J"),
- NATIVE_METHOD(Unsafe, freeMemory, "!(J)V"),
- NATIVE_METHOD(Unsafe, setMemory, "!(JJB)V"),
- NATIVE_METHOD(Unsafe, copyMemory, "!(JJJ)V"),
- NATIVE_METHOD(Unsafe, copyMemoryToPrimitiveArray, "!(JLjava/lang/Object;JJ)V"),
- NATIVE_METHOD(Unsafe, copyMemoryFromPrimitiveArray, "!(Ljava/lang/Object;JJJ)V"),
- NATIVE_METHOD(Unsafe, getBoolean, "!(Ljava/lang/Object;J)Z"),
+ FAST_NATIVE_METHOD(Unsafe, compareAndSwapInt, "(Ljava/lang/Object;JII)Z"),
+ FAST_NATIVE_METHOD(Unsafe, compareAndSwapLong, "(Ljava/lang/Object;JJJ)Z"),
+ FAST_NATIVE_METHOD(Unsafe, compareAndSwapObject, "(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z"),
+ FAST_NATIVE_METHOD(Unsafe, getIntVolatile, "(Ljava/lang/Object;J)I"),
+ FAST_NATIVE_METHOD(Unsafe, putIntVolatile, "(Ljava/lang/Object;JI)V"),
+ FAST_NATIVE_METHOD(Unsafe, getLongVolatile, "(Ljava/lang/Object;J)J"),
+ FAST_NATIVE_METHOD(Unsafe, putLongVolatile, "(Ljava/lang/Object;JJ)V"),
+ FAST_NATIVE_METHOD(Unsafe, getObjectVolatile, "(Ljava/lang/Object;J)Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(Unsafe, putObjectVolatile, "(Ljava/lang/Object;JLjava/lang/Object;)V"),
+ FAST_NATIVE_METHOD(Unsafe, getInt, "(Ljava/lang/Object;J)I"),
+ FAST_NATIVE_METHOD(Unsafe, putInt, "(Ljava/lang/Object;JI)V"),
+ FAST_NATIVE_METHOD(Unsafe, putOrderedInt, "(Ljava/lang/Object;JI)V"),
+ FAST_NATIVE_METHOD(Unsafe, getLong, "(Ljava/lang/Object;J)J"),
+ FAST_NATIVE_METHOD(Unsafe, putLong, "(Ljava/lang/Object;JJ)V"),
+ FAST_NATIVE_METHOD(Unsafe, putOrderedLong, "(Ljava/lang/Object;JJ)V"),
+ FAST_NATIVE_METHOD(Unsafe, getObject, "(Ljava/lang/Object;J)Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(Unsafe, putObject, "(Ljava/lang/Object;JLjava/lang/Object;)V"),
+ FAST_NATIVE_METHOD(Unsafe, putOrderedObject, "(Ljava/lang/Object;JLjava/lang/Object;)V"),
+ FAST_NATIVE_METHOD(Unsafe, getArrayBaseOffsetForComponentType, "(Ljava/lang/Class;)I"),
+ FAST_NATIVE_METHOD(Unsafe, getArrayIndexScaleForComponentType, "(Ljava/lang/Class;)I"),
+ FAST_NATIVE_METHOD(Unsafe, addressSize, "()I"),
+ FAST_NATIVE_METHOD(Unsafe, pageSize, "()I"),
+ FAST_NATIVE_METHOD(Unsafe, allocateMemory, "(J)J"),
+ FAST_NATIVE_METHOD(Unsafe, freeMemory, "(J)V"),
+ FAST_NATIVE_METHOD(Unsafe, setMemory, "(JJB)V"),
+ FAST_NATIVE_METHOD(Unsafe, copyMemory, "(JJJ)V"),
+ FAST_NATIVE_METHOD(Unsafe, copyMemoryToPrimitiveArray, "(JLjava/lang/Object;JJ)V"),
+ FAST_NATIVE_METHOD(Unsafe, copyMemoryFromPrimitiveArray, "(Ljava/lang/Object;JJJ)V"),
+ FAST_NATIVE_METHOD(Unsafe, getBoolean, "(Ljava/lang/Object;J)Z"),
- NATIVE_METHOD(Unsafe, getByte, "!(Ljava/lang/Object;J)B"),
- NATIVE_METHOD(Unsafe, getChar, "!(Ljava/lang/Object;J)C"),
- NATIVE_METHOD(Unsafe, getShort, "!(Ljava/lang/Object;J)S"),
- NATIVE_METHOD(Unsafe, getFloat, "!(Ljava/lang/Object;J)F"),
- NATIVE_METHOD(Unsafe, getDouble, "!(Ljava/lang/Object;J)D"),
- NATIVE_METHOD(Unsafe, putBoolean, "!(Ljava/lang/Object;JZ)V"),
- NATIVE_METHOD(Unsafe, putByte, "!(Ljava/lang/Object;JB)V"),
- NATIVE_METHOD(Unsafe, putChar, "!(Ljava/lang/Object;JC)V"),
- NATIVE_METHOD(Unsafe, putShort, "!(Ljava/lang/Object;JS)V"),
- NATIVE_METHOD(Unsafe, putFloat, "!(Ljava/lang/Object;JF)V"),
- NATIVE_METHOD(Unsafe, putDouble, "!(Ljava/lang/Object;JD)V"),
+ FAST_NATIVE_METHOD(Unsafe, getByte, "(Ljava/lang/Object;J)B"),
+ FAST_NATIVE_METHOD(Unsafe, getChar, "(Ljava/lang/Object;J)C"),
+ FAST_NATIVE_METHOD(Unsafe, getShort, "(Ljava/lang/Object;J)S"),
+ FAST_NATIVE_METHOD(Unsafe, getFloat, "(Ljava/lang/Object;J)F"),
+ FAST_NATIVE_METHOD(Unsafe, getDouble, "(Ljava/lang/Object;J)D"),
+ FAST_NATIVE_METHOD(Unsafe, putBoolean, "(Ljava/lang/Object;JZ)V"),
+ FAST_NATIVE_METHOD(Unsafe, putByte, "(Ljava/lang/Object;JB)V"),
+ FAST_NATIVE_METHOD(Unsafe, putChar, "(Ljava/lang/Object;JC)V"),
+ FAST_NATIVE_METHOD(Unsafe, putShort, "(Ljava/lang/Object;JS)V"),
+ FAST_NATIVE_METHOD(Unsafe, putFloat, "(Ljava/lang/Object;JF)V"),
+ FAST_NATIVE_METHOD(Unsafe, putDouble, "(Ljava/lang/Object;JD)V"),
// Each of the getFoo variants are overloaded with a call that operates
// directively on a native pointer.
- OVERLOADED_NATIVE_METHOD(Unsafe, getByte, "!(J)B", getByteJ),
- OVERLOADED_NATIVE_METHOD(Unsafe, getChar, "!(J)C", getCharJ),
- OVERLOADED_NATIVE_METHOD(Unsafe, getShort, "!(J)S", getShortJ),
- OVERLOADED_NATIVE_METHOD(Unsafe, getInt, "!(J)I", getIntJ),
- OVERLOADED_NATIVE_METHOD(Unsafe, getLong, "!(J)J", getLongJ),
- OVERLOADED_NATIVE_METHOD(Unsafe, getFloat, "!(J)F", getFloatJ),
- OVERLOADED_NATIVE_METHOD(Unsafe, getDouble, "!(J)D", getDoubleJ),
- OVERLOADED_NATIVE_METHOD(Unsafe, putByte, "!(JB)V", putByteJB),
- OVERLOADED_NATIVE_METHOD(Unsafe, putChar, "!(JC)V", putCharJC),
- OVERLOADED_NATIVE_METHOD(Unsafe, putShort, "!(JS)V", putShortJS),
- OVERLOADED_NATIVE_METHOD(Unsafe, putInt, "!(JI)V", putIntJI),
- OVERLOADED_NATIVE_METHOD(Unsafe, putLong, "!(JJ)V", putLongJJ),
- OVERLOADED_NATIVE_METHOD(Unsafe, putFloat, "!(JF)V", putFloatJF),
- OVERLOADED_NATIVE_METHOD(Unsafe, putDouble, "!(JD)V", putDoubleJD),
+ OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getByte, "(J)B", getByteJ),
+ OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getChar, "(J)C", getCharJ),
+ OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getShort, "(J)S", getShortJ),
+ OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getInt, "(J)I", getIntJ),
+ OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getLong, "(J)J", getLongJ),
+ OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getFloat, "(J)F", getFloatJ),
+ OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getDouble, "(J)D", getDoubleJ),
+ OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putByte, "(JB)V", putByteJB),
+ OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putChar, "(JC)V", putCharJC),
+ OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putShort, "(JS)V", putShortJS),
+ OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putInt, "(JI)V", putIntJI),
+ OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putLong, "(JJ)V", putLongJJ),
+ OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putFloat, "(JF)V", putFloatJF),
+ OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putDouble, "(JD)V", putDoubleJD),
// CAS
- NATIVE_METHOD(Unsafe, loadFence, "!()V"),
- NATIVE_METHOD(Unsafe, storeFence, "!()V"),
- NATIVE_METHOD(Unsafe, fullFence, "!()V"),
+ FAST_NATIVE_METHOD(Unsafe, loadFence, "()V"),
+ FAST_NATIVE_METHOD(Unsafe, storeFence, "()V"),
+ FAST_NATIVE_METHOD(Unsafe, fullFence, "()V"),
};
void register_sun_misc_Unsafe(JNIEnv* env) {
diff --git a/runtime/oat.h b/runtime/oat.h
index 656b868..1544121 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,7 +32,7 @@
class PACKED(4) OatHeader {
public:
static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
- static constexpr uint8_t kOatVersion[] = { '1', '1', '3', '\0' }; // Invoke info change.
+ static constexpr uint8_t kOatVersion[] = { '1', '1', '4', '\0' }; // hash-based DexCache types.
static constexpr const char* kImageLocationKey = "image-location";
static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
diff --git a/runtime/obj_ptr-inl.h b/runtime/obj_ptr-inl.h
index d0be6dc..f2921da 100644
--- a/runtime/obj_ptr-inl.h
+++ b/runtime/obj_ptr-inl.h
@@ -22,27 +22,27 @@
namespace art {
-template<class MirrorType, bool kPoison>
-inline bool ObjPtr<MirrorType, kPoison>::IsValid() const {
- if (!kPoison || IsNull()) {
+template<class MirrorType>
+inline bool ObjPtr<MirrorType>::IsValid() const {
+ if (!kObjPtrPoisoning || IsNull()) {
return true;
}
return GetCookie() == TrimCookie(Thread::Current()->GetPoisonObjectCookie());
}
-template<class MirrorType, bool kPoison>
-inline void ObjPtr<MirrorType, kPoison>::AssertValid() const {
- if (kPoison) {
+template<class MirrorType>
+inline void ObjPtr<MirrorType>::AssertValid() const {
+ if (kObjPtrPoisoning) {
CHECK(IsValid()) << "Stale object pointer " << PtrUnchecked() << " , expected cookie "
<< TrimCookie(Thread::Current()->GetPoisonObjectCookie()) << " but got " << GetCookie();
}
}
-template<class MirrorType, bool kPoison>
-inline uintptr_t ObjPtr<MirrorType, kPoison>::Encode(MirrorType* ptr) {
+template<class MirrorType>
+inline uintptr_t ObjPtr<MirrorType>::Encode(MirrorType* ptr) {
uintptr_t ref = reinterpret_cast<uintptr_t>(ptr);
DCHECK_ALIGNED(ref, kObjectAlignment);
- if (kPoison && ref != 0) {
+ if (kObjPtrPoisoning && ref != 0) {
DCHECK_LE(ref, 0xFFFFFFFFU);
ref >>= kObjectAlignmentShift;
// Put cookie in high bits.
@@ -53,8 +53,8 @@
return ref;
}
-template<class MirrorType, bool kPoison>
-inline std::ostream& operator<<(std::ostream& os, ObjPtr<MirrorType, kPoison> ptr) {
+template<class MirrorType>
+inline std::ostream& operator<<(std::ostream& os, ObjPtr<MirrorType> ptr) {
// May be used for dumping bad pointers, do not use the checked version.
return os << ptr.PtrUnchecked();
}
diff --git a/runtime/obj_ptr.h b/runtime/obj_ptr.h
index 2da2ae5..92cf4eb 100644
--- a/runtime/obj_ptr.h
+++ b/runtime/obj_ptr.h
@@ -26,10 +26,12 @@
namespace art {
+constexpr bool kObjPtrPoisoning = kIsDebugBuild;
+
// Value type representing a pointer to a mirror::Object of type MirrorType
// Pass kPoison as a template boolean for testing in non-debug builds.
// Since the cookie is thread based, it is not safe to share an ObjPtr between threads.
-template<class MirrorType, bool kPoison = kIsDebugBuild>
+template<class MirrorType>
class ObjPtr {
static constexpr size_t kCookieShift =
sizeof(kHeapReferenceSize) * kBitsPerByte - kObjectAlignmentShift;
@@ -60,14 +62,14 @@
template <typename Type,
typename = typename std::enable_if<std::is_base_of<MirrorType, Type>::value>::type>
- ALWAYS_INLINE ObjPtr(const ObjPtr<Type, kPoison>& other) // NOLINT
+ ALWAYS_INLINE ObjPtr(const ObjPtr<Type>& other) // NOLINT
REQUIRES_SHARED(Locks::mutator_lock_)
: reference_(Encode(static_cast<MirrorType*>(other.Ptr()))) {
}
template <typename Type,
typename = typename std::enable_if<std::is_base_of<MirrorType, Type>::value>::type>
- ALWAYS_INLINE ObjPtr& operator=(const ObjPtr<Type, kPoison>& other)
+ ALWAYS_INLINE ObjPtr& operator=(const ObjPtr<Type>& other)
REQUIRES_SHARED(Locks::mutator_lock_) {
reference_ = Encode(static_cast<MirrorType*>(other.Ptr()));
return *this;
@@ -130,7 +132,7 @@
// Ptr unchecked does not check that object pointer is valid. Do not use if you can avoid it.
ALWAYS_INLINE MirrorType* PtrUnchecked() const {
- if (kPoison) {
+ if (kObjPtrPoisoning) {
return reinterpret_cast<MirrorType*>(
static_cast<uintptr_t>(static_cast<uint32_t>(reference_ << kObjectAlignmentShift)));
} else {
@@ -167,46 +169,46 @@
// Hash function for stl data structures.
class HashObjPtr {
public:
- template<class MirrorType, bool kPoison>
- size_t operator()(const ObjPtr<MirrorType, kPoison>& ptr) const NO_THREAD_SAFETY_ANALYSIS {
+ template<class MirrorType>
+ size_t operator()(const ObjPtr<MirrorType>& ptr) const NO_THREAD_SAFETY_ANALYSIS {
return std::hash<MirrorType*>()(ptr.Ptr());
}
};
-template<class MirrorType, bool kPoison, typename PointerType>
-ALWAYS_INLINE bool operator==(const PointerType* a, const ObjPtr<MirrorType, kPoison>& b)
+template<class MirrorType, typename PointerType>
+ALWAYS_INLINE bool operator==(const PointerType* a, const ObjPtr<MirrorType>& b)
REQUIRES_SHARED(Locks::mutator_lock_) {
return b == a;
}
-template<class MirrorType, bool kPoison>
-ALWAYS_INLINE bool operator==(std::nullptr_t, const ObjPtr<MirrorType, kPoison>& b) {
+template<class MirrorType>
+ALWAYS_INLINE bool operator==(std::nullptr_t, const ObjPtr<MirrorType>& b) {
return b == nullptr;
}
-template<typename MirrorType, bool kPoison, typename PointerType>
-ALWAYS_INLINE bool operator!=(const PointerType* a, const ObjPtr<MirrorType, kPoison>& b)
+template<typename MirrorType, typename PointerType>
+ALWAYS_INLINE bool operator!=(const PointerType* a, const ObjPtr<MirrorType>& b)
REQUIRES_SHARED(Locks::mutator_lock_) {
return b != a;
}
-template<class MirrorType, bool kPoison>
-ALWAYS_INLINE bool operator!=(std::nullptr_t, const ObjPtr<MirrorType, kPoison>& b) {
+template<class MirrorType>
+ALWAYS_INLINE bool operator!=(std::nullptr_t, const ObjPtr<MirrorType>& b) {
return b != nullptr;
}
-template<class MirrorType, bool kPoison = kIsDebugBuild>
-static inline ObjPtr<MirrorType, kPoison> MakeObjPtr(MirrorType* ptr) {
- return ObjPtr<MirrorType, kPoison>(ptr);
+template<class MirrorType>
+static inline ObjPtr<MirrorType> MakeObjPtr(MirrorType* ptr) {
+ return ObjPtr<MirrorType>(ptr);
}
-template<class MirrorType, bool kPoison = kIsDebugBuild>
-static inline ObjPtr<MirrorType, kPoison> MakeObjPtr(ObjPtr<MirrorType, kPoison> ptr) {
- return ObjPtr<MirrorType, kPoison>(ptr);
+template<class MirrorType>
+static inline ObjPtr<MirrorType> MakeObjPtr(ObjPtr<MirrorType> ptr) {
+ return ObjPtr<MirrorType>(ptr);
}
-template<class MirrorType, bool kPoison>
-ALWAYS_INLINE std::ostream& operator<<(std::ostream& os, ObjPtr<MirrorType, kPoison> ptr);
+template<class MirrorType>
+ALWAYS_INLINE std::ostream& operator<<(std::ostream& os, ObjPtr<MirrorType> ptr);
} // namespace art
diff --git a/runtime/object_callbacks.h b/runtime/object_callbacks.h
index 4d726ec..ea5e698 100644
--- a/runtime/object_callbacks.h
+++ b/runtime/object_callbacks.h
@@ -43,7 +43,8 @@
// May return the same address as the input if the object did not move.
virtual mirror::Object* MarkObject(mirror::Object* obj) = 0;
// Mark an object and update the value stored in the heap reference if the object moved.
- virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj) = 0;
+ virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj,
+ bool do_atomic_update) = 0;
};
} // namespace art
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index a815a60..77ca9ce 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -1188,13 +1188,13 @@
ENSURE_NON_NULL(name_ptr);
switch (error) {
#define ERROR_CASE(e) case (JVMTI_ERROR_ ## e) : do { \
- jvmtiError res = CopyString(env, \
- "JVMTI_ERROR_"#e, \
- reinterpret_cast<unsigned char**>(name_ptr)); \
- if (res != OK) { \
+ jvmtiError res; \
+ JvmtiUniquePtr<char[]> copy = CopyString(env, "JVMTI_ERROR_"#e, &res); \
+ if (copy == nullptr) { \
*name_ptr = nullptr; \
return res; \
} else { \
+ *name_ptr = copy.release(); \
return OK; \
} \
} while (false)
@@ -1248,13 +1248,13 @@
ERROR_CASE(INVALID_ENVIRONMENT);
#undef ERROR_CASE
default: {
- jvmtiError res = CopyString(env,
- "JVMTI_ERROR_UNKNOWN",
- reinterpret_cast<unsigned char**>(name_ptr));
- if (res != OK) {
+ jvmtiError res;
+ JvmtiUniquePtr<char[]> copy = CopyString(env, "JVMTI_ERROR_UNKNOWN", &res);
+ if (copy == nullptr) {
*name_ptr = nullptr;
return res;
} else {
+ *name_ptr = copy.release();
return ERR(ILLEGAL_ARGUMENT);
}
}
diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h
index 106165c..99139a1 100644
--- a/runtime/openjdkjvmti/art_jvmti.h
+++ b/runtime/openjdkjvmti/art_jvmti.h
@@ -33,6 +33,7 @@
#define ART_RUNTIME_OPENJDKJVMTI_ART_JVMTI_H_
#include <memory>
+#include <type_traits>
#include <jni.h>
@@ -86,6 +87,7 @@
return ret_value;
}
+template <typename T>
class JvmtiDeleter {
public:
JvmtiDeleter() : env_(nullptr) {}
@@ -95,9 +97,9 @@
JvmtiDeleter(JvmtiDeleter&&) = default;
JvmtiDeleter& operator=(const JvmtiDeleter&) = default;
- void operator()(unsigned char* ptr) const {
+ void operator()(T* ptr) const {
CHECK(env_ != nullptr);
- jvmtiError ret = env_->Deallocate(ptr);
+ jvmtiError ret = env_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
CHECK(ret == ERR(NONE));
}
@@ -105,12 +107,65 @@
mutable jvmtiEnv* env_;
};
-using JvmtiUniquePtr = std::unique_ptr<unsigned char, JvmtiDeleter>;
+template <typename T>
+class JvmtiDeleter<T[]> {
+ public:
+ JvmtiDeleter() : env_(nullptr) {}
+ explicit JvmtiDeleter(jvmtiEnv* env) : env_(env) {}
+
+ JvmtiDeleter(JvmtiDeleter&) = default;
+ JvmtiDeleter(JvmtiDeleter&&) = default;
+ JvmtiDeleter& operator=(const JvmtiDeleter&) = default;
+
+ template <typename U>
+ void operator()(U* ptr) const {
+ CHECK(env_ != nullptr);
+ jvmtiError ret = env_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
+ CHECK(ret == ERR(NONE));
+ }
+
+ private:
+ mutable jvmtiEnv* env_;
+};
+
+template <typename T>
+using JvmtiUniquePtr = std::unique_ptr<T, JvmtiDeleter<T>>;
template <typename T>
ALWAYS_INLINE
-static inline JvmtiUniquePtr MakeJvmtiUniquePtr(jvmtiEnv* env, T* mem) {
- return JvmtiUniquePtr(reinterpret_cast<unsigned char*>(mem), JvmtiDeleter(env));
+static inline JvmtiUniquePtr<T> MakeJvmtiUniquePtr(jvmtiEnv* env, T* mem) {
+ return JvmtiUniquePtr<T>(mem, JvmtiDeleter<T>(env));
+}
+
+template <typename T>
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<T> MakeJvmtiUniquePtr(jvmtiEnv* env, unsigned char* mem) {
+ return JvmtiUniquePtr<T>(reinterpret_cast<T*>(mem), JvmtiDeleter<T>(env));
+}
+
+template <typename T>
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<T> AllocJvmtiUniquePtr(jvmtiEnv* env, jvmtiError* error) {
+ unsigned char* tmp;
+ *error = env->Allocate(sizeof(T), &tmp);
+ if (*error != ERR(NONE)) {
+ return JvmtiUniquePtr<T>();
+ }
+ return JvmtiUniquePtr<T>(tmp, JvmtiDeleter<T>(env));
+}
+
+template <typename T>
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<T> AllocJvmtiUniquePtr(jvmtiEnv* env,
+ size_t count,
+ jvmtiError* error) {
+ unsigned char* tmp;
+ *error = env->Allocate(sizeof(typename std::remove_extent<T>::type) * count, &tmp);
+ if (*error != ERR(NONE)) {
+ return JvmtiUniquePtr<T>();
+ }
+ return JvmtiUniquePtr<T>(reinterpret_cast<typename std::remove_extent<T>::type*>(tmp),
+ JvmtiDeleter<T>(env));
}
ALWAYS_INLINE
@@ -129,15 +184,12 @@
}
ALWAYS_INLINE
-static inline jvmtiError CopyString(jvmtiEnv* env, const char* src, unsigned char** copy) {
+static inline JvmtiUniquePtr<char[]> CopyString(jvmtiEnv* env, const char* src, jvmtiError* error) {
size_t len = strlen(src) + 1;
- unsigned char* buf;
- jvmtiError ret = env->Allocate(len, &buf);
- if (ret != ERR(NONE)) {
- return ret;
+ JvmtiUniquePtr<char[]> ret = AllocJvmtiUniquePtr<char[]>(env, len, error);
+ if (ret != nullptr) {
+ strcpy(ret.get(), src);
}
- strcpy(reinterpret_cast<char*>(buf), src);
- *copy = buf;
return ret;
}
diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc
index a8a0ded..4282e38 100644
--- a/runtime/openjdkjvmti/ti_class.cc
+++ b/runtime/openjdkjvmti/ti_class.cc
@@ -673,18 +673,17 @@
return ERR(INVALID_CLASS);
}
- JvmtiUniquePtr sig_copy;
+ JvmtiUniquePtr<char[]> sig_copy;
if (signature_ptr != nullptr) {
std::string storage;
const char* descriptor = klass->GetDescriptor(&storage);
- unsigned char* tmp;
- jvmtiError ret = CopyString(env, descriptor, &tmp);
- if (ret != ERR(NONE)) {
+ jvmtiError ret;
+ sig_copy = CopyString(env, descriptor, &ret);
+ if (sig_copy == nullptr) {
return ret;
}
- sig_copy = MakeJvmtiUniquePtr(env, tmp);
- *signature_ptr = reinterpret_cast<char*>(tmp);
+ *signature_ptr = sig_copy.get();
}
if (generic_ptr != nullptr) {
@@ -700,12 +699,12 @@
oss << str_array->Get(i)->ToModifiedUtf8();
}
std::string output_string = oss.str();
- unsigned char* tmp;
- jvmtiError ret = CopyString(env, output_string.c_str(), &tmp);
- if (ret != ERR(NONE)) {
+ jvmtiError ret;
+ JvmtiUniquePtr<char[]> copy = CopyString(env, output_string.c_str(), &ret);
+ if (copy == nullptr) {
return ret;
}
- *generic_ptr = reinterpret_cast<char*>(tmp);
+ *generic_ptr = copy.release();
} else if (soa.Self()->IsExceptionPending()) {
// TODO: Should we report an error here?
soa.Self()->ClearException();
diff --git a/runtime/openjdkjvmti/ti_class_definition.h b/runtime/openjdkjvmti/ti_class_definition.h
index dbe5da2..3c251d4 100644
--- a/runtime/openjdkjvmti/ti_class_definition.h
+++ b/runtime/openjdkjvmti/ti_class_definition.h
@@ -46,7 +46,7 @@
std::string name;
jobject protection_domain;
jint dex_len;
- JvmtiUniquePtr dex_data;
+ JvmtiUniquePtr<unsigned char> dex_data;
art::ArraySlice<const unsigned char> original_dex_file;
ArtClassDefinition() = default;
diff --git a/runtime/openjdkjvmti/ti_class_loader.cc b/runtime/openjdkjvmti/ti_class_loader.cc
index d05f579..66357eb 100644
--- a/runtime/openjdkjvmti/ti_class_loader.cc
+++ b/runtime/openjdkjvmti/ti_class_loader.cc
@@ -105,7 +105,6 @@
// mCookie is nulled out if the DexFile has been closed but mInternalCookie sticks around until
// the object is finalized. Since they always point to the same array if mCookie is not null we
// just use the mInternalCookie field. We will update one or both of these fields later.
- // TODO Should I get the class from the classloader or directly?
art::ArtField* internal_cookie_field = java_dex_file_obj->GetClass()->FindDeclaredInstanceField(
"mInternalCookie", "Ljava/lang/Object;");
// TODO Add check that mCookie is either null or same as mInternalCookie
@@ -113,7 +112,6 @@
return internal_cookie_field->GetObject(java_dex_file_obj.Get())->AsLongArray();
}
-// TODO Really wishing I had that mirror of java.lang.DexFile now.
art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::AllocateNewDexFileCookie(
art::Thread* self,
art::Handle<art::mirror::LongArray> cookie,
@@ -128,8 +126,6 @@
return nullptr;
}
// Copy the oat-dex field at the start.
- // TODO Should I clear this field?
- // TODO This is a really crappy thing here with the first element being different.
new_cookie->SetWithoutChecks<false>(0, cookie->GetWithoutChecks(0));
// This must match the casts in runtime/native/dalvik_system_DexFile.cc:ConvertDexFilesToJavaArray
new_cookie->SetWithoutChecks<false>(
diff --git a/runtime/openjdkjvmti/ti_field.cc b/runtime/openjdkjvmti/ti_field.cc
index 131e6c3..8c3f2ff 100644
--- a/runtime/openjdkjvmti/ti_field.cc
+++ b/runtime/openjdkjvmti/ti_field.cc
@@ -63,31 +63,29 @@
art::ScopedObjectAccess soa(art::Thread::Current());
art::ArtField* art_field = art::jni::DecodeArtField(field);
- JvmtiUniquePtr name_copy;
+ JvmtiUniquePtr<char[]> name_copy;
if (name_ptr != nullptr) {
const char* field_name = art_field->GetName();
if (field_name == nullptr) {
field_name = "<error>";
}
- unsigned char* tmp;
- jvmtiError ret = CopyString(env, field_name, &tmp);
- if (ret != ERR(NONE)) {
+ jvmtiError ret;
+ name_copy = CopyString(env, field_name, &ret);
+ if (name_copy == nullptr) {
return ret;
}
- name_copy = MakeJvmtiUniquePtr(env, tmp);
- *name_ptr = reinterpret_cast<char*>(tmp);
+ *name_ptr = name_copy.get();
}
- JvmtiUniquePtr signature_copy;
+ JvmtiUniquePtr<char[]> signature_copy;
if (signature_ptr != nullptr) {
const char* sig = art_field->GetTypeDescriptor();
- unsigned char* tmp;
- jvmtiError ret = CopyString(env, sig, &tmp);
- if (ret != ERR(NONE)) {
+ jvmtiError ret;
+ signature_copy = CopyString(env, sig, &ret);
+ if (signature_copy == nullptr) {
return ret;
}
- signature_copy = MakeJvmtiUniquePtr(env, tmp);
- *signature_ptr = reinterpret_cast<char*>(tmp);
+ *signature_ptr = signature_copy.get();
}
// TODO: Support generic signature.
@@ -102,12 +100,12 @@
oss << str_array->Get(i)->ToModifiedUtf8();
}
std::string output_string = oss.str();
- unsigned char* tmp;
- jvmtiError ret = CopyString(env, output_string.c_str(), &tmp);
- if (ret != ERR(NONE)) {
+ jvmtiError ret;
+ JvmtiUniquePtr<char[]> copy = CopyString(env, output_string.c_str(), &ret);
+ if (copy == nullptr) {
return ret;
}
- *generic_ptr = reinterpret_cast<char*>(tmp);
+ *generic_ptr = copy.release();
} else if (soa.Self()->IsExceptionPending()) {
// TODO: Should we report an error here?
soa.Self()->ClearException();
diff --git a/runtime/openjdkjvmti/ti_heap.cc b/runtime/openjdkjvmti/ti_heap.cc
index fe3e52b..2fbc12b 100644
--- a/runtime/openjdkjvmti/ti_heap.cc
+++ b/runtime/openjdkjvmti/ti_heap.cc
@@ -31,6 +31,7 @@
#include "object_callbacks.h"
#include "object_tagging.h"
#include "obj_ptr-inl.h"
+#include "primitive.h"
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
#include "thread-inl.h"
@@ -38,14 +39,138 @@
namespace openjdkjvmti {
+namespace {
+
+// Report the contents of a string, if a callback is set.
+jint ReportString(art::ObjPtr<art::mirror::Object> obj,
+ jvmtiEnv* env,
+ ObjectTagTable* tag_table,
+ const jvmtiHeapCallbacks* cb,
+ const void* user_data) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (UNLIKELY(cb->string_primitive_value_callback != nullptr) && obj->IsString()) {
+ art::ObjPtr<art::mirror::String> str = obj->AsString();
+ int32_t string_length = str->GetLength();
+ jvmtiError alloc_error;
+ JvmtiUniquePtr<uint16_t[]> data = AllocJvmtiUniquePtr<uint16_t[]>(env,
+ string_length,
+ &alloc_error);
+ if (data == nullptr) {
+ // TODO: Not really sure what to do here. Should we abort the iteration and go all the way
+ // back? For now just warn.
+ LOG(WARNING) << "Unable to allocate buffer for string reporting! Silently dropping value.";
+ return 0;
+ }
+
+ if (str->IsCompressed()) {
+ uint8_t* compressed_data = str->GetValueCompressed();
+ for (int32_t i = 0; i != string_length; ++i) {
+ data[i] = compressed_data[i];
+ }
+ } else {
+ // Can copy directly.
+ memcpy(data.get(), str->GetValue(), string_length * sizeof(uint16_t));
+ }
+
+ const jlong class_tag = tag_table->GetTagOrZero(obj->GetClass());
+ jlong string_tag = tag_table->GetTagOrZero(obj.Ptr());
+ const jlong saved_string_tag = string_tag;
+
+ jint result = cb->string_primitive_value_callback(class_tag,
+ obj->SizeOf(),
+ &string_tag,
+ data.get(),
+ string_length,
+ const_cast<void*>(user_data));
+ if (string_tag != saved_string_tag) {
+ tag_table->Set(obj.Ptr(), string_tag);
+ }
+
+ return result;
+ }
+ return 0;
+}
+
+// Report the contents of a primitive array, if a callback is set.
+jint ReportPrimitiveArray(art::ObjPtr<art::mirror::Object> obj,
+ jvmtiEnv* env,
+ ObjectTagTable* tag_table,
+ const jvmtiHeapCallbacks* cb,
+ const void* user_data) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (UNLIKELY(cb->array_primitive_value_callback != nullptr) &&
+ obj->IsArrayInstance() &&
+ !obj->IsObjectArray()) {
+ art::ObjPtr<art::mirror::Array> array = obj->AsArray();
+ int32_t array_length = array->GetLength();
+ size_t component_size = array->GetClass()->GetComponentSize();
+ art::Primitive::Type art_prim_type = array->GetClass()->GetComponentType()->GetPrimitiveType();
+ jvmtiPrimitiveType prim_type =
+ static_cast<jvmtiPrimitiveType>(art::Primitive::Descriptor(art_prim_type)[0]);
+ DCHECK(prim_type == JVMTI_PRIMITIVE_TYPE_BOOLEAN ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_BYTE ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_CHAR ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_SHORT ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_INT ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_LONG ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_FLOAT ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_DOUBLE);
+
+ const jlong class_tag = tag_table->GetTagOrZero(obj->GetClass());
+ jlong array_tag = tag_table->GetTagOrZero(obj.Ptr());
+ const jlong saved_array_tag = array_tag;
+
+ jint result;
+ if (array_length == 0) {
+ result = cb->array_primitive_value_callback(class_tag,
+ obj->SizeOf(),
+ &array_tag,
+ 0,
+ prim_type,
+ nullptr,
+ const_cast<void*>(user_data));
+ } else {
+ jvmtiError alloc_error;
+ JvmtiUniquePtr<char[]> data = AllocJvmtiUniquePtr<char[]>(env,
+ array_length * component_size,
+ &alloc_error);
+ if (data == nullptr) {
+ // TODO: Not really sure what to do here. Should we abort the iteration and go all the way
+ // back? For now just warn.
+ LOG(WARNING) << "Unable to allocate buffer for array reporting! Silently dropping value.";
+ return 0;
+ }
+
+ memcpy(data.get(), array->GetRawData(component_size, 0), array_length * component_size);
+
+ result = cb->array_primitive_value_callback(class_tag,
+ obj->SizeOf(),
+ &array_tag,
+ array_length,
+ prim_type,
+ data.get(),
+ const_cast<void*>(user_data));
+ }
+
+ if (array_tag != saved_array_tag) {
+ tag_table->Set(obj.Ptr(), array_tag);
+ }
+
+ return result;
+ }
+ return 0;
+}
+
+} // namespace
+
struct IterateThroughHeapData {
IterateThroughHeapData(HeapUtil* _heap_util,
+ jvmtiEnv* _env,
jint heap_filter,
art::ObjPtr<art::mirror::Class> klass,
const jvmtiHeapCallbacks* _callbacks,
const void* _user_data)
: heap_util(_heap_util),
filter_klass(klass),
+ env(_env),
callbacks(_callbacks),
user_data(_user_data),
filter_out_tagged((heap_filter & JVMTI_HEAP_FILTER_TAGGED) != 0),
@@ -78,6 +203,7 @@
HeapUtil* heap_util;
art::ObjPtr<art::mirror::Class> filter_klass;
+ jvmtiEnv* env;
const jvmtiHeapCallbacks* callbacks;
const void* user_data;
const bool filter_out_tagged;
@@ -111,8 +237,6 @@
return;
}
- // TODO: Handle array_primitive_value_callback.
-
if (ithd->filter_klass != nullptr) {
if (ithd->filter_klass != klass) {
return;
@@ -139,11 +263,28 @@
ithd->stop_reports = (ret & JVMTI_VISIT_ABORT) != 0;
- // TODO Implement array primitive and string primitive callback.
+ if (!ithd->stop_reports) {
+ jint string_ret = ReportString(obj,
+ ithd->env,
+ ithd->heap_util->GetTags(),
+ ithd->callbacks,
+ ithd->user_data);
+ ithd->stop_reports = (string_ret & JVMTI_VISIT_ABORT) != 0;
+ }
+
+ if (!ithd->stop_reports) {
+ jint array_ret = ReportPrimitiveArray(obj,
+ ithd->env,
+ ithd->heap_util->GetTags(),
+ ithd->callbacks,
+ ithd->user_data);
+ ithd->stop_reports = (array_ret & JVMTI_VISIT_ABORT) != 0;
+ }
+
// TODO Implement primitive field callback.
}
-jvmtiError HeapUtil::IterateThroughHeap(jvmtiEnv* env ATTRIBUTE_UNUSED,
+jvmtiError HeapUtil::IterateThroughHeap(jvmtiEnv* env,
jint heap_filter,
jclass klass,
const jvmtiHeapCallbacks* callbacks,
@@ -152,15 +293,11 @@
return ERR(NULL_POINTER);
}
- if (callbacks->array_primitive_value_callback != nullptr) {
- // TODO: Implement.
- return ERR(NOT_IMPLEMENTED);
- }
-
art::Thread* self = art::Thread::Current();
art::ScopedObjectAccess soa(self); // Now we know we have the shared lock.
IterateThroughHeapData ithd(this,
+ env,
heap_filter,
soa.Decode<art::mirror::Class>(klass),
callbacks,
@@ -174,10 +311,12 @@
class FollowReferencesHelper FINAL {
public:
FollowReferencesHelper(HeapUtil* h,
+ jvmtiEnv* jvmti_env,
art::ObjPtr<art::mirror::Object> initial_object,
const jvmtiHeapCallbacks* callbacks,
const void* user_data)
- : tag_table_(h->GetTags()),
+ : env(jvmti_env),
+ tag_table_(h->GetTags()),
initial_object_(initial_object),
callbacks_(callbacks),
user_data_(user_data),
@@ -467,6 +606,11 @@
obj->VisitReferences<false>(visitor, art::VoidFunctor());
stop_reports_ = visitor.stop_reports;
+
+ if (!stop_reports_) {
+ jint string_ret = ReportString(obj, env, tag_table_, callbacks_, user_data_);
+ stop_reports_ = (string_ret & JVMTI_VISIT_ABORT) != 0;
+ }
}
void VisitArray(art::mirror::Object* array)
@@ -498,6 +642,11 @@
}
}
}
+ } else {
+ if (!stop_reports_) {
+ jint array_ret = ReportPrimitiveArray(array, env, tag_table_, callbacks_, user_data_);
+ stop_reports_ = (array_ret & JVMTI_VISIT_ABORT) != 0;
+ }
}
}
@@ -655,6 +804,7 @@
return result;
}
+ jvmtiEnv* env;
ObjectTagTable* tag_table_;
art::ObjPtr<art::mirror::Object> initial_object_;
const jvmtiHeapCallbacks* callbacks_;
@@ -671,7 +821,7 @@
friend class CollectAndReportRootsVisitor;
};
-jvmtiError HeapUtil::FollowReferences(jvmtiEnv* env ATTRIBUTE_UNUSED,
+jvmtiError HeapUtil::FollowReferences(jvmtiEnv* env,
jint heap_filter ATTRIBUTE_UNUSED,
jclass klass ATTRIBUTE_UNUSED,
jobject initial_object,
@@ -681,11 +831,6 @@
return ERR(NULL_POINTER);
}
- if (callbacks->array_primitive_value_callback != nullptr) {
- // TODO: Implement.
- return ERR(NOT_IMPLEMENTED);
- }
-
art::Thread* self = art::Thread::Current();
art::gc::Heap* heap = art::Runtime::Current()->GetHeap();
@@ -700,6 +845,7 @@
art::ScopedSuspendAll ssa("FollowReferences");
FollowReferencesHelper frh(this,
+ env,
self->DecodeJObject(initial_object),
callbacks,
user_data);
diff --git a/runtime/openjdkjvmti/ti_method.cc b/runtime/openjdkjvmti/ti_method.cc
index a6cfcc1..bc73029 100644
--- a/runtime/openjdkjvmti/ti_method.cc
+++ b/runtime/openjdkjvmti/ti_method.cc
@@ -110,35 +110,32 @@
art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
art_method = art_method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
- JvmtiUniquePtr name_copy;
+ JvmtiUniquePtr<char[]> name_copy;
if (name_ptr != nullptr) {
const char* method_name = art_method->GetName();
if (method_name == nullptr) {
method_name = "<error>";
}
- unsigned char* tmp;
- jvmtiError ret = CopyString(env, method_name, &tmp);
- if (ret != ERR(NONE)) {
+ jvmtiError ret;
+ name_copy = CopyString(env, method_name, &ret);
+ if (name_copy == nullptr) {
return ret;
}
- name_copy = MakeJvmtiUniquePtr(env, tmp);
- *name_ptr = reinterpret_cast<char*>(tmp);
+ *name_ptr = name_copy.get();
}
- JvmtiUniquePtr signature_copy;
+ JvmtiUniquePtr<char[]> signature_copy;
if (signature_ptr != nullptr) {
const art::Signature sig = art_method->GetSignature();
std::string str = sig.ToString();
- unsigned char* tmp;
- jvmtiError ret = CopyString(env, str.c_str(), &tmp);
- if (ret != ERR(NONE)) {
+ jvmtiError ret;
+ signature_copy = CopyString(env, str.c_str(), &ret);
+ if (signature_copy == nullptr) {
return ret;
}
- signature_copy = MakeJvmtiUniquePtr(env, tmp);
- *signature_ptr = reinterpret_cast<char*>(tmp);
+ *signature_ptr = signature_copy.get();
}
- // TODO: Support generic signature.
if (generic_ptr != nullptr) {
*generic_ptr = nullptr;
if (!art_method->GetDeclaringClass()->IsProxyClass()) {
@@ -150,12 +147,12 @@
oss << str_array->Get(i)->ToModifiedUtf8();
}
std::string output_string = oss.str();
- unsigned char* tmp;
- jvmtiError ret = CopyString(env, output_string.c_str(), &tmp);
- if (ret != ERR(NONE)) {
+ jvmtiError ret;
+ JvmtiUniquePtr<char[]> generic_copy = CopyString(env, output_string.c_str(), &ret);
+ if (generic_copy == nullptr) {
return ret;
}
- *generic_ptr = reinterpret_cast<char*>(tmp);
+ *generic_ptr = generic_copy.release();
} else if (soa.Self()->IsExceptionPending()) {
// TODO: Should we report an error here?
soa.Self()->ClearException();
diff --git a/runtime/openjdkjvmti/ti_properties.cc b/runtime/openjdkjvmti/ti_properties.cc
index 46b9e71..4f4f013 100644
--- a/runtime/openjdkjvmti/ti_properties.cc
+++ b/runtime/openjdkjvmti/ti_properties.cc
@@ -82,71 +82,69 @@
static constexpr const char* kPropertyLibraryPath = "java.library.path";
static constexpr const char* kPropertyClassPath = "java.class.path";
-static jvmtiError Copy(jvmtiEnv* env, const char* in, char** out) {
- unsigned char* data = nullptr;
- jvmtiError result = CopyString(env, in, &data);
- *out = reinterpret_cast<char*>(data);
- return result;
-}
-
jvmtiError PropertiesUtil::GetSystemProperties(jvmtiEnv* env,
jint* count_ptr,
char*** property_ptr) {
if (count_ptr == nullptr || property_ptr == nullptr) {
return ERR(NULL_POINTER);
}
- unsigned char* array_data;
- jvmtiError array_alloc_result = env->Allocate((kPropertiesSize + 2) * sizeof(char*), &array_data);
- if (array_alloc_result != ERR(NONE)) {
+ jvmtiError array_alloc_result;
+ JvmtiUniquePtr<char*[]> array_data_ptr = AllocJvmtiUniquePtr<char*[]>(env,
+ kPropertiesSize + 2,
+ &array_alloc_result);
+ if (array_data_ptr == nullptr) {
return array_alloc_result;
}
- JvmtiUniquePtr array_data_ptr = MakeJvmtiUniquePtr(env, array_data);
- char** array = reinterpret_cast<char**>(array_data);
- std::vector<JvmtiUniquePtr> property_copies;
+ std::vector<JvmtiUniquePtr<char[]>> property_copies;
{
- char* libpath_data;
- jvmtiError libpath_result = Copy(env, kPropertyLibraryPath, &libpath_data);
- if (libpath_result != ERR(NONE)) {
+ jvmtiError libpath_result;
+ JvmtiUniquePtr<char[]> libpath_data = CopyString(env, kPropertyLibraryPath, &libpath_result);
+ if (libpath_data == nullptr) {
return libpath_result;
}
- array[0] = libpath_data;
- property_copies.push_back(MakeJvmtiUniquePtr(env, libpath_data));
+ array_data_ptr.get()[0] = libpath_data.get();
+ property_copies.push_back(std::move(libpath_data));
}
{
- char* classpath_data;
- jvmtiError classpath_result = Copy(env, kPropertyClassPath, &classpath_data);
- if (classpath_result != ERR(NONE)) {
+ jvmtiError classpath_result;
+ JvmtiUniquePtr<char[]> classpath_data = CopyString(env, kPropertyClassPath, &classpath_result);
+ if (classpath_data == nullptr) {
return classpath_result;
}
- array[1] = classpath_data;
- property_copies.push_back(MakeJvmtiUniquePtr(env, classpath_data));
+ array_data_ptr.get()[1] = classpath_data.get();
+ property_copies.push_back(std::move(classpath_data));
}
for (size_t i = 0; i != kPropertiesSize; ++i) {
- char* data;
- jvmtiError data_result = Copy(env, kProperties[i][0], &data);
- if (data_result != ERR(NONE)) {
+ jvmtiError data_result;
+ JvmtiUniquePtr<char[]> data = CopyString(env, kProperties[i][0], &data_result);
+ if (data == nullptr) {
return data_result;
}
- array[i + 2] = data;
- property_copies.push_back(MakeJvmtiUniquePtr(env, data));
+ array_data_ptr.get()[i + 2] = data.get();
+ property_copies.push_back(std::move(data));
}
// Everything is OK, release the data.
- array_data_ptr.release();
+ *count_ptr = kPropertiesSize + 2;
+ *property_ptr = array_data_ptr.release();
for (auto& uptr : property_copies) {
uptr.release();
}
- *count_ptr = kPropertiesSize + 2;
- *property_ptr = array;
-
return ERR(NONE);
}
+static jvmtiError Copy(jvmtiEnv* env, const char* in, char** out) {
+ jvmtiError result;
+ JvmtiUniquePtr<char[]> data = CopyString(env, in, &result);
+ *out = data.release();
+ return result;
+}
+
jvmtiError PropertiesUtil::GetSystemProperty(jvmtiEnv* env,
const char* property,
char** value_ptr) {
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc
index 8436045..eefd012 100644
--- a/runtime/openjdkjvmti/ti_redefine.cc
+++ b/runtime/openjdkjvmti/ti_redefine.cc
@@ -170,10 +170,6 @@
// We cannot ensure that the right dex file is used in inlined frames so we don't support
// redefining them.
DCHECK(!IsInInlinedFrame()) << "Inlined frames are not supported when using redefinition";
- // TODO We should really support intrinsic obsolete methods.
- // TODO We should really support redefining intrinsics.
- // We don't support intrinsics so check for them here.
- DCHECK(!old_method->IsIntrinsic());
art::ArtMethod* new_obsolete_method = obsolete_maps_->FindObsoleteVersion(old_method);
if (new_obsolete_method == nullptr) {
// Create a new Obsolete Method and put it in the list.
@@ -241,6 +237,9 @@
} else if (klass->IsInterface()) {
*error_msg = "Modification of Interface classes is currently not supported";
return ERR(UNMODIFIABLE_CLASS);
+ } else if (klass->IsStringClass()) {
+ *error_msg = "Modification of String class is not supported";
+ return ERR(UNMODIFIABLE_CLASS);
} else if (klass->IsArrayClass()) {
*error_msg = "Modification of Array classes is not supported";
return ERR(UNMODIFIABLE_CLASS);
@@ -323,7 +322,6 @@
// This makes cleanup easier (since we unambiguously own the bytes) and also is useful since we
// will need to keep the original bytes around unaltered for subsequent RetransformClasses calls
// to get the passed in bytes.
- // TODO Implement saving the original bytes.
unsigned char* class_bytes_copy = nullptr;
jvmtiError res = env->Allocate(definitions[i].class_byte_count, &class_bytes_copy);
if (res != OK) {
@@ -396,8 +394,8 @@
*error_msg_ = "Unable to get class signature!";
return ret;
}
- JvmtiUniquePtr generic_unique_ptr(MakeJvmtiUniquePtr(env, generic_ptr_unused));
- JvmtiUniquePtr signature_unique_ptr(MakeJvmtiUniquePtr(env, signature_ptr));
+ JvmtiUniquePtr<char> generic_unique_ptr(MakeJvmtiUniquePtr(env, generic_ptr_unused));
+ JvmtiUniquePtr<char> signature_unique_ptr(MakeJvmtiUniquePtr(env, signature_ptr));
std::unique_ptr<art::MemMap> map(MoveDataToMemMap(original_dex_location,
def.dex_len,
def.dex_data.get(),
@@ -518,6 +516,11 @@
CallbackCtx ctx(&map, linker->GetAllocatorForClassLoader(art_klass->GetClassLoader()));
// Add all the declared methods to the map
for (auto& m : art_klass->GetDeclaredMethods(art::kRuntimePointerSize)) {
+ if (m.IsIntrinsic()) {
+ LOG(WARNING) << "Redefining intrinsic method " << m.PrettyMethod() << ". This may cause the "
+ << "unexpected use of the original definition of " << m.PrettyMethod() << "in "
+ << "methods that have already been compiled.";
+ }
// It is possible to simply filter out some methods where they cannot really become obsolete,
// such as native methods and keep their original (possibly optimized) implementations. We don't
// do this, however, since we would need to mark these functions (still in the classes
@@ -526,8 +529,6 @@
// error checking from the interpreter which ensure we don't try to start executing obsolete
// methods.
ctx.obsolete_methods.insert(&m);
- // TODO Allow this or check in IsModifiableClass.
- DCHECK(!m.IsIntrinsic());
}
{
art::MutexLock mu(driver_->self_, *art::Locks::thread_list_lock_);
@@ -674,7 +675,6 @@
}
bool Redefiner::ClassRedefinition::CheckClass() {
- // TODO Might just want to put it in a ObjPtr and NoSuspend assert.
art::StackHandleScope<1> hs(driver_->self_);
// Easy check that only 1 class def is present.
if (dex_file_->NumClassDefs() != 1) {
@@ -750,7 +750,6 @@
return true;
}
-// TODO Move this to use IsRedefinable when that function is made.
bool Redefiner::ClassRedefinition::CheckRedefinable() {
std::string err;
art::StackHandleScope<1> hs(driver_->self_);
@@ -883,7 +882,6 @@
DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder);
};
-// TODO Stash and update soft failure state
bool Redefiner::ClassRedefinition::CheckVerification(int32_t klass_index,
const RedefinitionDataHolder& holder) {
DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
@@ -976,7 +974,6 @@
ClassLoaderHelper::FindSourceDexFileObject(driver_->self_, loader)));
holder->SetJavaDexFile(klass_index, dex_file_obj.Get());
if (dex_file_obj == nullptr) {
- // TODO Better error msg.
RecordFailure(ERR(INTERNAL), "Unable to find dex file!");
return false;
}
@@ -1124,11 +1121,6 @@
self_->TransitionFromRunnableToSuspended(art::ThreadState::kNative);
runtime_->GetThreadList()->SuspendAll(
"Final installation of redefined Classes!", /*long_suspend*/true);
- // TODO We need to invalidate all breakpoints in the redefined class with the debugger.
- // TODO We need to deal with any instrumentation/debugger deoptimized_methods_.
- // TODO We need to update all debugger MethodIDs so they note the method they point to is
- // obsolete or implement some other well defined semantics.
- // TODO We need to decide on & implement semantics for JNI jmethodids when we redefine methods.
counter = 0;
for (Redefiner::ClassRedefinition& redef : redefinitions_) {
art::ScopedAssertNoThreadSuspension nts("Updating runtime objects for redefinition");
@@ -1143,6 +1135,10 @@
holder.GetOriginalDexFileBytes(counter));
counter++;
}
+ // TODO We should check for if any of the redefined methods are intrinsic methods here and, if any
+ // are, force a full-world deoptimization before finishing redefinition. If we don't do this then
+ // methods that have been jitted prior to the current redefinition being applied might continue
+ // to use the old versions of the intrinsics!
// TODO Shrink the obsolete method maps if possible?
// TODO Put this into a scoped thing.
runtime_->GetThreadList()->ResumeAll();
@@ -1181,18 +1177,18 @@
}
const art::DexFile::ProtoId* proto_id = dex_file_->FindProtoId(method_return_idx,
new_type_list);
- // TODO Return false, cleanup.
CHECK(proto_id != nullptr || old_type_list == nullptr);
const art::DexFile::MethodId* method_id = dex_file_->FindMethodId(declaring_class_id,
*new_name_id,
*proto_id);
- // TODO Return false, cleanup.
CHECK(method_id != nullptr);
uint32_t dex_method_idx = dex_file_->GetIndexForMethodId(*method_id);
method.SetDexMethodIndex(dex_method_idx);
linker->SetEntryPointsToInterpreter(&method);
method.SetCodeItemOffset(dex_file_->FindCodeItemOffset(class_def, dex_method_idx));
method.SetDexCacheResolvedMethods(new_dex_cache->GetResolvedMethods(), image_pointer_size);
+ // Clear all the intrinsics related flags.
+ method.ClearAccessFlags(art::kAccIntrinsic | (~art::kAccFlagsNotUsedByIntrinsic));
// Notify the jit that this method is redefined.
art::jit::Jit* jit = driver_->runtime_->GetJit();
if (jit != nullptr) {
@@ -1210,7 +1206,6 @@
dex_file_->FindTypeId(field.GetDeclaringClass()->GetDescriptor(&declaring_class_name));
const art::DexFile::StringId* new_name_id = dex_file_->FindStringId(field.GetName());
const art::DexFile::TypeId* new_type_id = dex_file_->FindTypeId(field.GetTypeDescriptor());
- // TODO Handle error, cleanup.
CHECK(new_name_id != nullptr && new_type_id != nullptr && new_declaring_id != nullptr);
const art::DexFile::FieldId* new_field_id =
dex_file_->FindFieldId(*new_declaring_id, *new_name_id, *new_type_id);
diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc
index f8f8fa6..788ac30 100644
--- a/runtime/openjdkjvmti/ti_thread.cc
+++ b/runtime/openjdkjvmti/ti_thread.cc
@@ -186,17 +186,17 @@
return ERR(INVALID_THREAD);
}
- JvmtiUniquePtr name_uptr;
+ JvmtiUniquePtr<char[]> name_uptr;
if (self != nullptr) {
// Have a native thread object, this thread is alive.
std::string name;
self->GetThreadName(name);
- jvmtiError name_result = CopyString(
- env, name.c_str(), reinterpret_cast<unsigned char**>(&info_ptr->name));
- if (name_result != ERR(NONE)) {
+ jvmtiError name_result;
+ name_uptr = CopyString(env, name.c_str(), &name_result);
+ if (name_uptr == nullptr) {
return name_result;
}
- name_uptr = MakeJvmtiUniquePtr(env, info_ptr->name);
+ info_ptr->name = name_uptr.get();
info_ptr->priority = self->GetNativePriority();
@@ -239,12 +239,12 @@
} else {
name_cstr = "";
}
- jvmtiError name_result = CopyString(
- env, name_cstr, reinterpret_cast<unsigned char**>(&info_ptr->name));
- if (name_result != ERR(NONE)) {
+ jvmtiError name_result;
+ name_uptr = CopyString(env, name_cstr, &name_result);
+ if (name_uptr == nullptr) {
return name_result;
}
- name_uptr = MakeJvmtiUniquePtr(env, info_ptr->name);
+ info_ptr->name = name_uptr.get();
}
// Priority.
diff --git a/runtime/openjdkjvmti/ti_threadgroup.cc b/runtime/openjdkjvmti/ti_threadgroup.cc
index 1423874..df14333 100644
--- a/runtime/openjdkjvmti/ti_threadgroup.cc
+++ b/runtime/openjdkjvmti/ti_threadgroup.cc
@@ -116,11 +116,12 @@
tmp_str = name_obj->ToModifiedUtf8();
tmp_cstr = tmp_str.c_str();
}
- jvmtiError result =
- CopyString(env, tmp_cstr, reinterpret_cast<unsigned char**>(&info_ptr->name));
- if (result != ERR(NONE)) {
+ jvmtiError result;
+ JvmtiUniquePtr<char[]> copy = CopyString(env, tmp_cstr, &result);
+ if (copy == nullptr) {
return result;
}
+ info_ptr->name = copy.release();
}
// Parent.
@@ -239,45 +240,38 @@
std::vector<art::ObjPtr<art::mirror::Object>> thread_groups;
GetChildThreadGroups(thread_group, &thread_groups);
- jthread* thread_data = nullptr;
- JvmtiUniquePtr peers_uptr;
+ JvmtiUniquePtr<jthread[]> peers_uptr;
if (!thread_peers.empty()) {
- unsigned char* data;
- jvmtiError res = env->Allocate(sizeof(jthread) * thread_peers.size(), &data);
- if (res != ERR(NONE)) {
+ jvmtiError res;
+ peers_uptr = AllocJvmtiUniquePtr<jthread[]>(env, thread_peers.size(), &res);
+ if (peers_uptr == nullptr) {
return res;
}
- thread_data = reinterpret_cast<jthread*>(data);
- peers_uptr = MakeJvmtiUniquePtr(env, data);
}
- jthreadGroup* group_data = nullptr;
+ JvmtiUniquePtr<jthreadGroup[]> group_uptr;
if (!thread_groups.empty()) {
- unsigned char* data;
- jvmtiError res = env->Allocate(sizeof(jthreadGroup) * thread_groups.size(), &data);
- if (res != ERR(NONE)) {
+ jvmtiError res;
+ group_uptr = AllocJvmtiUniquePtr<jthreadGroup[]>(env, thread_groups.size(), &res);
+ if (group_uptr == nullptr) {
return res;
}
- group_data = reinterpret_cast<jthreadGroup*>(data);
}
// Can't fail anymore from here on.
// Copy data into out buffers.
for (size_t i = 0; i != thread_peers.size(); ++i) {
- thread_data[i] = soa.AddLocalReference<jthread>(thread_peers[i]);
+ peers_uptr[i] = soa.AddLocalReference<jthread>(thread_peers[i]);
}
for (size_t i = 0; i != thread_groups.size(); ++i) {
- group_data[i] = soa.AddLocalReference<jthreadGroup>(thread_groups[i]);
+ group_uptr[i] = soa.AddLocalReference<jthreadGroup>(thread_groups[i]);
}
*thread_count_ptr = static_cast<jint>(thread_peers.size());
- *threads_ptr = thread_data;
+ *threads_ptr = peers_uptr.release();
*group_count_ptr = static_cast<jint>(thread_groups.size());
- *groups_ptr = group_data;
-
- // Everything's fine.
- peers_uptr.release();
+ *groups_ptr = group_uptr.release();
return ERR(NONE);
}
diff --git a/runtime/read_barrier-inl.h b/runtime/read_barrier-inl.h
index 37cf257..2b38b2e 100644
--- a/runtime/read_barrier-inl.h
+++ b/runtime/read_barrier-inl.h
@@ -198,7 +198,7 @@
inline void ReadBarrier::AssertToSpaceInvariant(mirror::Object* obj, MemberOffset offset,
mirror::Object* ref) {
- if (kEnableToSpaceInvariantChecks || kIsDebugBuild) {
+ if (kEnableToSpaceInvariantChecks) {
if (ref == nullptr || IsDuringStartup()) {
return;
}
@@ -209,7 +209,7 @@
inline void ReadBarrier::AssertToSpaceInvariant(GcRootSource* gc_root_source,
mirror::Object* ref) {
- if (kEnableToSpaceInvariantChecks || kIsDebugBuild) {
+ if (kEnableToSpaceInvariantChecks) {
if (ref == nullptr || IsDuringStartup()) {
return;
}
diff --git a/runtime/safe_map.h b/runtime/safe_map.h
index 49f80f3..e638fdb 100644
--- a/runtime/safe_map.h
+++ b/runtime/safe_map.h
@@ -137,6 +137,16 @@
return it->second;
}
+ iterator FindOrAdd(const K& k, const V& v) {
+ iterator it = find(k);
+ return it == end() ? Put(k, v) : it;
+ }
+
+ iterator FindOrAdd(const K& k) {
+ iterator it = find(k);
+ return it == end() ? Put(k, V()) : it;
+ }
+
bool Equals(const Self& rhs) const {
return map_ == rhs.map_;
}
diff --git a/runtime/scoped_thread_state_change-inl.h b/runtime/scoped_thread_state_change-inl.h
index 000da59..c817a9e 100644
--- a/runtime/scoped_thread_state_change-inl.h
+++ b/runtime/scoped_thread_state_change-inl.h
@@ -79,11 +79,11 @@
return obj == nullptr ? nullptr : Env()->AddLocalReference<T>(obj);
}
-template<typename T, bool kPoison>
-inline ObjPtr<T, kPoison> ScopedObjectAccessAlreadyRunnable::Decode(jobject obj) const {
+template<typename T>
+inline ObjPtr<T> ScopedObjectAccessAlreadyRunnable::Decode(jobject obj) const {
Locks::mutator_lock_->AssertSharedHeld(Self());
DCHECK(IsRunnable()); // Don't work with raw objects in non-runnable states.
- return ObjPtr<T, kPoison>::DownCast(Self()->DecodeJObject(obj));
+ return ObjPtr<T>::DownCast(Self()->DecodeJObject(obj));
}
inline bool ScopedObjectAccessAlreadyRunnable::IsRunnable() const {
diff --git a/runtime/scoped_thread_state_change.h b/runtime/scoped_thread_state_change.h
index 24199f7..a3286ac 100644
--- a/runtime/scoped_thread_state_change.h
+++ b/runtime/scoped_thread_state_change.h
@@ -27,7 +27,7 @@
namespace art {
struct JNIEnvExt;
-template<class MirrorType, bool kPoison> class ObjPtr;
+template<class MirrorType> class ObjPtr;
// Scoped change into and out of a particular state. Handles Runnable transitions that require
// more complicated suspension checking. The subclasses ScopedObjectAccessUnchecked and
@@ -91,8 +91,8 @@
T AddLocalReference(ObjPtr<mirror::Object> obj) const
REQUIRES_SHARED(Locks::mutator_lock_);
- template<typename T, bool kPoison = kIsDebugBuild>
- ObjPtr<T, kPoison> Decode(jobject obj) const REQUIRES_SHARED(Locks::mutator_lock_);
+ template<typename T>
+ ObjPtr<T> Decode(jobject obj) const REQUIRES_SHARED(Locks::mutator_lock_);
ALWAYS_INLINE bool IsRunnable() const;
diff --git a/runtime/thread-inl.h b/runtime/thread-inl.h
index 8d94626..482e0e3 100644
--- a/runtime/thread-inl.h
+++ b/runtime/thread-inl.h
@@ -29,6 +29,7 @@
#include "base/mutex-inl.h"
#include "gc/heap.h"
#include "jni_env_ext.h"
+#include "obj_ptr.h"
#include "runtime.h"
#include "thread_pool.h"
@@ -355,7 +356,7 @@
}
inline void Thread::PoisonObjectPointersIfDebug() {
- if (kIsDebugBuild) {
+ if (kObjPtrPoisoning) {
Thread::Current()->PoisonObjectPointers();
}
}
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 3abb9fc..ff66cc1 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -2549,11 +2549,18 @@
return nullptr;
}
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;
+ if (line_number == -1) {
+ // Make the line_number field of StackTraceElement 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 = dex_pc;
+ } else {
+ 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;
+ }
}
}
}
@@ -2564,11 +2571,11 @@
if (method_name_object == nullptr) {
return nullptr;
}
- ObjPtr<mirror::StackTraceElement> obj =mirror::StackTraceElement::Alloc(soa.Self(),
- class_name_object,
- method_name_object,
- source_name_object,
- line_number);
+ ObjPtr<mirror::StackTraceElement> obj = mirror::StackTraceElement::Alloc(soa.Self(),
+ class_name_object,
+ method_name_object,
+ source_name_object,
+ line_number);
if (obj == nullptr) {
return nullptr;
}
diff --git a/runtime/utils/dex_cache_arrays_layout-inl.h b/runtime/utils/dex_cache_arrays_layout-inl.h
index 9865821..f9a1405 100644
--- a/runtime/utils/dex_cache_arrays_layout-inl.h
+++ b/runtime/utils/dex_cache_arrays_layout-inl.h
@@ -51,9 +51,11 @@
: DexCacheArraysLayout(pointer_size, dex_file->GetHeader(), dex_file->NumCallSiteIds()) {
}
-inline constexpr size_t DexCacheArraysLayout::Alignment() {
- // GcRoot<> alignment is 4, i.e. lower than or equal to the pointer alignment.
- static_assert(alignof(GcRoot<mirror::Class>) == 4, "Expecting alignof(GcRoot<>) == 4");
+constexpr size_t DexCacheArraysLayout::Alignment() {
+ // mirror::Type/String/MethodTypeDexCacheType alignment is 8,
+ // i.e. higher than or equal to the pointer alignment.
+ static_assert(alignof(mirror::TypeDexCacheType) == 8,
+ "Expecting alignof(ClassDexCacheType) == 8");
static_assert(alignof(mirror::StringDexCacheType) == 8,
"Expecting alignof(StringDexCacheType) == 8");
static_assert(alignof(mirror::MethodTypeDexCacheType) == 8,
@@ -63,17 +65,22 @@
}
template <typename T>
-static constexpr PointerSize GcRootAsPointerSize() {
+constexpr PointerSize GcRootAsPointerSize() {
static_assert(sizeof(GcRoot<T>) == 4U, "Unexpected GcRoot size");
return PointerSize::k32;
}
inline size_t DexCacheArraysLayout::TypeOffset(dex::TypeIndex type_idx) const {
- return types_offset_ + ElementOffset(GcRootAsPointerSize<mirror::Class>(), type_idx.index_);
+ return types_offset_ + ElementOffset(PointerSize::k64,
+ type_idx.index_ % mirror::DexCache::kDexCacheTypeCacheSize);
}
inline size_t DexCacheArraysLayout::TypesSize(size_t num_elements) const {
- return ArraySize(GcRootAsPointerSize<mirror::Class>(), num_elements);
+ size_t cache_size = mirror::DexCache::kDexCacheTypeCacheSize;
+ if (num_elements < cache_size) {
+ cache_size = num_elements;
+ }
+ return ArraySize(PointerSize::k64, cache_size);
}
inline size_t DexCacheArraysLayout::TypesAlignment() const {
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 16739fa..38d151b 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -2399,7 +2399,8 @@
const RegType& res_type = ResolveClassAndCheckAccess(type_idx);
if (res_type.IsConflict()) {
// If this is a primitive type, fail HARD.
- mirror::Class* klass = dex_cache_->GetResolvedType(type_idx);
+ ObjPtr<mirror::Class> klass =
+ ClassLinker::LookupResolvedType(type_idx, dex_cache_.Get(), class_loader_.Get());
if (klass != nullptr && klass->IsPrimitive()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "using primitive type "
<< dex_file_->StringByTypeIdx(type_idx) << " in instanceof in "
@@ -3722,9 +3723,16 @@
}
const RegType& MethodVerifier::ResolveClassAndCheckAccess(dex::TypeIndex class_idx) {
- mirror::Class* klass = dex_cache_->GetResolvedType(class_idx);
+ mirror::Class* klass = can_load_classes_
+ ? Runtime::Current()->GetClassLinker()->ResolveType(
+ *dex_file_, class_idx, dex_cache_, class_loader_)
+ : ClassLinker::LookupResolvedType(class_idx, dex_cache_.Get(), class_loader_.Get()).Ptr();
+ if (can_load_classes_ && klass == nullptr) {
+ DCHECK(self_->IsExceptionPending());
+ self_->ClearException();
+ }
const RegType* result = nullptr;
- if (klass != nullptr) {
+ if (klass != nullptr && !klass->IsErroneous()) {
bool precise = klass->CannotBeAssignedFromOtherTypes();
if (precise && !IsInstantiableOrPrimitive(klass)) {
const char* descriptor = dex_file_->StringByTypeIdx(class_idx);
@@ -3747,10 +3755,6 @@
<< "' in " << GetDeclaringClass();
return *result;
}
- if (klass == nullptr && !result->IsUnresolvedTypes()) {
- klass = result->GetClass();
- dex_cache_->SetResolvedType(class_idx, klass);
- }
// Record result of class resolution attempt.
VerifierDeps::MaybeRecordClassResolution(*dex_file_, class_idx, klass);
diff --git a/test/100-reflect2/expected.txt b/test/100-reflect2/expected.txt
index dd89d64..e2a1001 100644
--- a/test/100-reflect2/expected.txt
+++ b/test/100-reflect2/expected.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), native void java.lang.String.setCharAt(int,char), private boolean java.lang.String.nonSyncContentEquals(java.lang.AbstractStringBuilder), private int java.lang.String.indexOfSupplementary(int,int), private int java.lang.String.lastIndexOfSupplementary(int,int), private native int java.lang.String.fastIndexOf(int,int), private native java.lang.String java.lang.String.fastSubstring(int,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.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.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.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 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(java.lang.String,java.lang.String,int), static int java.lang.String.lastIndexOf(char[],int,int,char[],int,int,int), static int java.lang.String.lastIndexOf(java.lang.String,java.lang.String,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.indexOfSupplementary(int,int), private int java.lang.String.lastIndexOfSupplementary(int,int), private native int java.lang.String.fastIndexOf(int,int), private native java.lang.String java.lang.String.doReplace(char,char), private native java.lang.String java.lang.String.fastSubstring(int,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.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.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.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 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(java.lang.String,java.lang.String,int), static int java.lang.String.lastIndexOf(char[],int,int,char[],int,int,int), static int java.lang.String.lastIndexOf(java.lang.String,java.lang.String,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/155-java-set-resolved-type/src/Main.java b/test/155-java-set-resolved-type/src/Main.java
index f92363e..56b8c3e 100644
--- a/test/155-java-set-resolved-type/src/Main.java
+++ b/test/155-java-set-resolved-type/src/Main.java
@@ -55,11 +55,7 @@
Class<?> timpl = Class.forName("TestImplementation", false, mainLoader);
// Clear the dex cache resolved types to force a proper lookup the next time
// we need to find TestInterface.
- // TODO: Enable clearing the dex cache when we switch to the hash-based type array
- // and do a proper lookup. Currently, ClassLinker fully relies on the DexCache.
- if (false) {
- clearResolvedTypes(timpl);
- }
+ clearResolvedTypes(timpl);
// Force intialization of TestClass2. This expects the interface type to be
// resolved and found through simple lookup.
diff --git a/test/201-built-in-except-detail-messages/src/Main.java b/test/201-built-in-except-detail-messages/src/Main.java
index dc58819..c2976c8 100644
--- a/test/201-built-in-except-detail-messages/src/Main.java
+++ b/test/201-built-in-except-detail-messages/src/Main.java
@@ -411,7 +411,7 @@
m.invoke("hello", "world"); // Wrong type.
fail();
} catch (IllegalArgumentException iae) {
- assertEquals("method java.lang.String.charAt! argument 1 has type int, got java.lang.String",
+ assertEquals("method java.lang.String.charAt argument 1 has type int, got java.lang.String",
iae.getMessage());
}
try {
@@ -419,7 +419,7 @@
m.invoke("hello", (Object) null); // Null for a primitive argument.
fail();
} catch (IllegalArgumentException iae) {
- assertEquals("method java.lang.String.charAt! argument 1 has type int, got null",
+ assertEquals("method java.lang.String.charAt argument 1 has type int, got null",
iae.getMessage());
}
try {
diff --git a/test/551-checker-shifter-operand/src/Main.java b/test/551-checker-shifter-operand/src/Main.java
index a4561b8..e967398 100644
--- a/test/551-checker-shifter-operand/src/Main.java
+++ b/test/551-checker-shifter-operand/src/Main.java
@@ -76,6 +76,25 @@
* the shifter operand.
*/
+ /// CHECK-START-ARM: long Main.$opt$noinline$translate(long, byte) instruction_simplifier_arm (before)
+ /// CHECK-DAG: <<l:j\d+>> ParameterValue
+ /// CHECK-DAG: <<b:b\d+>> ParameterValue
+ /// CHECK: <<tmp:j\d+>> TypeConversion [<<b>>]
+ /// CHECK: Sub [<<l>>,<<tmp>>]
+
+ /// CHECK-START-ARM: long Main.$opt$noinline$translate(long, byte) instruction_simplifier_arm (after)
+ /// CHECK-DAG: <<l:j\d+>> ParameterValue
+ /// CHECK-DAG: <<b:b\d+>> ParameterValue
+ /// CHECK: DataProcWithShifterOp [<<l>>,<<b>>] kind:Sub+SXTB
+
+ /// CHECK-START-ARM: long Main.$opt$noinline$translate(long, byte) instruction_simplifier_arm (after)
+ /// CHECK-NOT: TypeConversion
+ /// CHECK-NOT: Sub
+
+ /// CHECK-START-ARM: long Main.$opt$noinline$translate(long, byte) disassembly (after)
+ /// CHECK: subs r{{\d+}}, r{{\d+}}, r{{\d+}}
+ /// CHECK: sbc r{{\d+}}, r{{\d+}}, r{{\d+}}, asr #31
+
/// CHECK-START-ARM64: long Main.$opt$noinline$translate(long, byte) instruction_simplifier_arm64 (before)
/// CHECK-DAG: <<l:j\d+>> ParameterValue
/// CHECK-DAG: <<b:b\d+>> ParameterValue
@@ -85,7 +104,7 @@
/// CHECK-START-ARM64: long Main.$opt$noinline$translate(long, byte) instruction_simplifier_arm64 (after)
/// CHECK-DAG: <<l:j\d+>> ParameterValue
/// CHECK-DAG: <<b:b\d+>> ParameterValue
- /// CHECK: Arm64DataProcWithShifterOp [<<l>>,<<b>>] kind:Sub+SXTB
+ /// CHECK: DataProcWithShifterOp [<<l>>,<<b>>] kind:Sub+SXTB
/// CHECK-START-ARM64: long Main.$opt$noinline$translate(long, byte) instruction_simplifier_arm64 (after)
/// CHECK-NOT: TypeConversion
@@ -106,6 +125,21 @@
* inputs are the the IR.
*/
+ /// CHECK-START-ARM: int Main.$opt$noinline$sameInput(int) instruction_simplifier_arm (before)
+ /// CHECK: <<a:i\d+>> ParameterValue
+ /// CHECK: <<Const2:i\d+>> IntConstant 2
+ /// CHECK: <<tmp:i\d+>> Shl [<<a>>,<<Const2>>]
+ /// CHECK: Add [<<tmp>>,<<tmp>>]
+
+ /// CHECK-START-ARM: int Main.$opt$noinline$sameInput(int) instruction_simplifier_arm (after)
+ /// CHECK-DAG: <<a:i\d+>> ParameterValue
+ /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2
+ /// CHECK: <<Shl:i\d+>> Shl [<<a>>,<<Const2>>]
+ /// CHECK: Add [<<Shl>>,<<Shl>>]
+
+ /// CHECK-START-ARM: int Main.$opt$noinline$sameInput(int) instruction_simplifier_arm (after)
+ /// CHECK-NOT: DataProcWithShifterOp
+
/// CHECK-START-ARM64: int Main.$opt$noinline$sameInput(int) instruction_simplifier_arm64 (before)
/// CHECK: <<a:i\d+>> ParameterValue
/// CHECK: <<Const2:i\d+>> IntConstant 2
@@ -119,7 +153,7 @@
/// CHECK: Add [<<Shl>>,<<Shl>>]
/// CHECK-START-ARM64: int Main.$opt$noinline$sameInput(int) instruction_simplifier_arm64 (after)
- /// CHECK-NOT: Arm64DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
public static int $opt$noinline$sameInput(int a) {
if (doThrow) throw new Error();
@@ -131,6 +165,28 @@
* Check that we perform the merge for multiple uses.
*/
+ /// CHECK-START-ARM: int Main.$opt$noinline$multipleUses(int) instruction_simplifier_arm (before)
+ /// CHECK: <<arg:i\d+>> ParameterValue
+ /// CHECK: <<Const23:i\d+>> IntConstant 23
+ /// CHECK: <<tmp:i\d+>> Shl [<<arg>>,<<Const23>>]
+ /// CHECK: Add [<<tmp>>,{{i\d+}}]
+ /// CHECK: Add [<<tmp>>,{{i\d+}}]
+ /// CHECK: Add [<<tmp>>,{{i\d+}}]
+ /// CHECK: Add [<<tmp>>,{{i\d+}}]
+ /// CHECK: Add [<<tmp>>,{{i\d+}}]
+
+ /// CHECK-START-ARM: int Main.$opt$noinline$multipleUses(int) instruction_simplifier_arm (after)
+ /// CHECK: <<arg:i\d+>> ParameterValue
+ /// CHECK: DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+ /// CHECK: DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+ /// CHECK: DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+ /// CHECK: DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+ /// CHECK: DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+
+ /// CHECK-START-ARM: int Main.$opt$noinline$multipleUses(int) instruction_simplifier_arm (after)
+ /// CHECK-NOT: Shl
+ /// CHECK-NOT: Add
+
/// CHECK-START-ARM64: int Main.$opt$noinline$multipleUses(int) instruction_simplifier_arm64 (before)
/// CHECK: <<arg:i\d+>> ParameterValue
/// CHECK: <<Const23:i\d+>> IntConstant 23
@@ -143,11 +199,11 @@
/// CHECK-START-ARM64: int Main.$opt$noinline$multipleUses(int) instruction_simplifier_arm64 (after)
/// CHECK: <<arg:i\d+>> ParameterValue
- /// CHECK: Arm64DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
- /// CHECK: Arm64DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
- /// CHECK: Arm64DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
- /// CHECK: Arm64DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
- /// CHECK: Arm64DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+ /// CHECK: DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+ /// CHECK: DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+ /// CHECK: DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+ /// CHECK: DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+ /// CHECK: DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
/// CHECK-START-ARM64: int Main.$opt$noinline$multipleUses(int) instruction_simplifier_arm64 (after)
/// CHECK-NOT: Shl
@@ -171,9 +227,19 @@
* operand, so test that only the shifts are merged.
*/
+ /// CHECK-START-ARM: void Main.$opt$noinline$testAnd(long, long) instruction_simplifier_arm (after)
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
+
+ /// CHECK-START-ARM: void Main.$opt$noinline$testAnd(long, long) disassembly (after)
+ /// CHECK: and lsl
+ /// CHECK: sbfx
+ /// CHECK: asr
+ /// CHECK: and
+
/// CHECK-START-ARM64: void Main.$opt$noinline$testAnd(long, long) instruction_simplifier_arm64 (after)
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK-NOT: Arm64DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$noinline$testAnd(long, long) disassembly (after)
/// CHECK: and lsl
@@ -186,9 +252,18 @@
(a & (b << 5)) | (a & (byte)b));
}
+ /// CHECK-START-ARM: void Main.$opt$noinline$testOr(int, int) instruction_simplifier_arm (after)
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
+
+ /// CHECK-START-ARM: void Main.$opt$noinline$testOr(int, int) disassembly (after)
+ /// CHECK: orr asr
+ /// CHECK: ubfx
+ /// CHECK: orr
+
/// CHECK-START-ARM64: void Main.$opt$noinline$testOr(int, int) instruction_simplifier_arm64 (after)
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK-NOT: Arm64DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$noinline$testOr(int, int) disassembly (after)
/// CHECK: orr asr
@@ -201,9 +276,19 @@
(a | (b >> 6)) | (a | (char)b));
}
+ /// CHECK-START-ARM: void Main.$opt$noinline$testXor(long, long) instruction_simplifier_arm (after)
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
+
+ /// CHECK-START-ARM: void Main.$opt$noinline$testXor(long, long) disassembly (after)
+ /// CHECK: eor lsr
+ /// CHECK: mov
+ /// CHECK: asr
+ /// CHECK: eor
+
/// CHECK-START-ARM64: void Main.$opt$noinline$testXor(long, long) instruction_simplifier_arm64 (after)
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK-NOT: Arm64DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$noinline$testXor(long, long) disassembly (after)
/// CHECK: eor lsr
@@ -216,9 +301,12 @@
(a ^ (b >>> 7)) | (a ^ (int)b));
}
+ /// CHECK-START-ARM: void Main.$opt$noinline$testNeg(int) instruction_simplifier_arm (after)
+ /// CHECK-NOT: DataProcWithShifterOp
+
/// CHECK-START-ARM64: void Main.$opt$noinline$testNeg(int) instruction_simplifier_arm64 (after)
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK-NOT: Arm64DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$noinline$testNeg(int) disassembly (after)
/// CHECK: neg lsl
@@ -239,9 +327,12 @@
* does occur on the right-hand.
*/
+ /// CHECK-START-ARM: void Main.$opt$validateExtendByteInt1(int, byte) instruction_simplifier_arm (after)
+ /// CHECK-NOT: DataProcWithShifterOp
+
/// CHECK-START-ARM64: void Main.$opt$validateExtendByteInt1(int, byte) instruction_simplifier_arm64 (after)
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK-NOT: Arm64DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$validateExtendByteInt1(int, byte) instruction_simplifier_arm64 (after)
/// CHECK-NOT: TypeConversion
@@ -252,9 +343,11 @@
assertIntEquals(a + $noinline$byteToShort(b), a + (short)b);
}
+ /// CHECK-START-ARM: void Main.$opt$validateExtendByteInt2(int, byte) instruction_simplifier_arm (after)
+ /// CHECK-NOT: DataProcWithShifterOp
+
/// CHECK-START-ARM64: void Main.$opt$validateExtendByteInt2(int, byte) instruction_simplifier_arm64 (after)
- /// CHECK-NOT: Arm64DataProcWithShifterOp
- /// CHECK-NOT: Arm64DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
public static void $opt$validateExtendByteInt2(int a, byte b) {
// The conversion to `int` has been optimized away, so there is nothing to merge.
@@ -263,13 +356,25 @@
assertLongEquals(a + $noinline$byteToLong(b), a + (long)b);
}
+ /// CHECK-START-ARM: void Main.$opt$validateExtendByteLong(long, byte) instruction_simplifier_arm (after)
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
+
+ /// CHECK-START-ARM: void Main.$opt$validateExtendByteLong(long, byte) instruction_simplifier_arm (after)
+ /// CHECK: TypeConversion
+ /// CHECK-NOT: TypeConversion
+
/// CHECK-START-ARM64: void Main.$opt$validateExtendByteLong(long, byte) instruction_simplifier_arm64 (after)
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK-NOT: Arm64DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$validateExtendByteLong(long, byte) instruction_simplifier_arm64 (after)
/// CHECK: TypeConversion
@@ -294,9 +399,12 @@
$opt$validateExtendByteLong(a, b);
}
+ /// CHECK-START-ARM: void Main.$opt$validateExtendCharInt1(int, char) instruction_simplifier_arm (after)
+ /// CHECK-NOT: DataProcWithShifterOp
+
/// CHECK-START-ARM64: void Main.$opt$validateExtendCharInt1(int, char) instruction_simplifier_arm64 (after)
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$validateExtendCharInt1(int, char) instruction_simplifier_arm64 (after)
/// CHECK-NOT: TypeConversion
@@ -306,22 +414,41 @@
assertIntEquals(a + $noinline$charToShort(b), a + (short)b);
}
+ /// CHECK-START-ARM: void Main.$opt$validateExtendCharInt2(int, char) instruction_simplifier_arm (after)
+ /// CHECK-NOT: DataProcWithShifterOp
+
/// CHECK-START-ARM64: void Main.$opt$validateExtendCharInt2(int, char) instruction_simplifier_arm64 (after)
- /// CHECK-NOT: Arm64DataProcWithShifterOp
- /// CHECK-NOT: Arm64DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
public static void $opt$validateExtendCharInt2(int a, char b) {
// The conversion to `int` has been optimized away, so there is nothing to merge.
assertIntEquals (a + $noinline$charToInt (b), a + (int)b);
- // There is an environment use for `(long)b`, preventing the merge.
+ // There is an environment use for `(long)b` and the implicit `(long)a`, preventing the merge.
assertLongEquals(a + $noinline$charToLong(b), a + (long)b);
}
+ /// CHECK-START-ARM: void Main.$opt$validateExtendCharLong(long, char) instruction_simplifier_arm (after)
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
+
+ /// CHECK-START-ARM: void Main.$opt$validateExtendCharLong(long, char) instruction_simplifier_arm (after)
+ /// CHECK: TypeConversion
+ /// CHECK: TypeConversion
+ /// CHECK-NOT: TypeConversion
+
/// CHECK-START-ARM64: void Main.$opt$validateExtendCharLong(long, char) instruction_simplifier_arm64 (after)
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$validateExtendCharLong(long, char) instruction_simplifier_arm64 (after)
/// CHECK: TypeConversion
@@ -332,7 +459,7 @@
// The first two tests have a type conversion.
assertLongEquals(a + $noinline$charToByte (b), a + (byte)b);
assertLongEquals(a + $noinline$charToShort(b), a + (short)b);
- // This test does not because the conversion to `int` is optimized away.
+ // On ARM64 this test does not because the conversion to `int` is optimized away.
assertLongEquals(a + $noinline$charToInt (b), a + (int)b);
}
@@ -342,9 +469,12 @@
$opt$validateExtendCharLong(a, b);
}
+ /// CHECK-START-ARM: void Main.$opt$validateExtendShortInt1(int, short) instruction_simplifier_arm (after)
+ /// CHECK-NOT: DataProcWithShifterOp
+
/// CHECK-START-ARM64: void Main.$opt$validateExtendShortInt1(int, short) instruction_simplifier_arm64 (after)
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$validateExtendShortInt1(int, short) instruction_simplifier_arm64 (after)
/// CHECK-NOT: TypeConversion
@@ -354,21 +484,41 @@
assertIntEquals(a + $noinline$shortToChar (b), a + (char)b);
}
+ /// CHECK-START-ARM: void Main.$opt$validateExtendShortInt2(int, short) instruction_simplifier_arm (after)
+ /// CHECK-NOT: DataProcWithShifterOp
+
/// CHECK-START-ARM64: void Main.$opt$validateExtendShortInt2(int, short) instruction_simplifier_arm64 (after)
- /// CHECK-NOT: Arm64DataProcWithShifterOp
- /// CHECK-NOT: Arm64DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
public static void $opt$validateExtendShortInt2(int a, short b) {
// The conversion to `int` has been optimized away, so there is nothing to merge.
assertIntEquals (a + $noinline$shortToInt (b), a + (int)b);
- // There is an environment use for `(long)b`, preventing the merge.
+ // There is an environment use for `(long)b` and the implicit `(long)a`, preventing the merge.
assertLongEquals(a + $noinline$shortToLong (b), a + (long)b);
}
+ /// CHECK-START-ARM: void Main.$opt$validateExtendShortLong(long, short) instruction_simplifier_arm (after)
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
+
+ /// CHECK-START-ARM: void Main.$opt$validateExtendShortLong(long, short) instruction_simplifier_arm (after)
+ /// CHECK: TypeConversion
+ /// CHECK: TypeConversion
+ /// CHECK-NOT: TypeConversion
+
/// CHECK-START-ARM64: void Main.$opt$validateExtendShortLong(long, short) instruction_simplifier_arm64 (after)
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$validateExtendShortLong(long, short) instruction_simplifier_arm64 (after)
/// CHECK: TypeConversion
@@ -379,7 +529,7 @@
// The first two tests have a type conversion.
assertLongEquals(a + $noinline$shortToByte(b), a + (byte)b);
assertLongEquals(a + $noinline$shortToChar(b), a + (char)b);
- // This test does not because the conversion to `int` is optimized away.
+ // On ARM64 this test does not because the conversion to `int` is optimized away.
assertLongEquals(a + $noinline$shortToInt (b), a + (int)b);
}
@@ -389,11 +539,31 @@
$opt$validateExtendShortLong(a, b);
}
+ /// CHECK-START-ARM: void Main.$opt$validateExtendInt(long, int) instruction_simplifier_arm (after)
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
+
+ /// CHECK-START-ARM: void Main.$opt$validateExtendInt(long, int) instruction_simplifier_arm (after)
+ /// CHECK: TypeConversion
+ /// CHECK: TypeConversion
+ /// CHECK: TypeConversion
+ /// CHECK-NOT: TypeConversion
+
/// CHECK-START-ARM64: void Main.$opt$validateExtendInt(long, int) instruction_simplifier_arm64 (after)
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$validateExtendInt(long, int) instruction_simplifier_arm64 (after)
/// CHECK: TypeConversion
@@ -411,11 +581,34 @@
assertLongEquals(a + $noinline$intToLong (b), a + (long)b);
}
+ /// CHECK-START-ARM: void Main.$opt$validateExtendLong(long, long) instruction_simplifier_arm (after)
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
+
+ /// CHECK-START-ARM: void Main.$opt$validateExtendLong(long, long) instruction_simplifier_arm (after)
+ /// CHECK: TypeConversion
+ /// CHECK: TypeConversion
+ /// CHECK: TypeConversion
+ /// CHECK: TypeConversion
+ /// CHECK-NOT: TypeConversion
+
/// CHECK-START-ARM64: void Main.$opt$validateExtendLong(long, long) instruction_simplifier_arm64 (after)
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$validateExtendLong(long, long) instruction_simplifier_arm64 (after)
/// CHECK: TypeConversion
@@ -449,40 +642,83 @@
// Each test line below should see one merge.
+ /// CHECK-START-ARM: void Main.$opt$validateShiftInt(int, int) instruction_simplifier_arm (after)
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
+ // Note: `b << 32`, `b >> 32` and `b >>> 32` are optimized away by generic simplifier.
+
+ /// CHECK-START-ARM: void Main.$opt$validateShiftInt(int, int) instruction_simplifier_arm (after)
+ /// CHECK-NOT: Shl
+ /// CHECK-NOT: Shr
+ /// CHECK-NOT: UShr
+
/// CHECK-START-ARM64: void Main.$opt$validateShiftInt(int, int) instruction_simplifier_arm64 (after)
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
// Note: `b << 32`, `b >> 32` and `b >>> 32` are optimized away by generic simplifier.
/// CHECK-START-ARM64: void Main.$opt$validateShiftInt(int, int) instruction_simplifier_arm64 (after)
@@ -552,43 +788,89 @@
}
// Each test line below should see one merge.
+ /// CHECK-START-ARM: void Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm (after)
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
+
+ // On ARM shifts by 1 are not merged.
+ /// CHECK-START-ARM: void Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm (after)
+ /// CHECK: Shl
+ /// CHECK-NOT: Shl
+ /// CHECK: Shr
+ /// CHECK-NOT: Shr
+ /// CHECK: UShr
+ /// CHECK-NOT: UShr
+
/// CHECK-START-ARM64: void Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm64 (after)
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
- /// CHECK: Arm64DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK-NOT: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm64 (after)
/// CHECK-NOT: Shl
diff --git a/test/593-checker-shift-and-simplifier/src/Main.java b/test/593-checker-shift-and-simplifier/src/Main.java
index 65e809a..c9826bc 100644
--- a/test/593-checker-shift-and-simplifier/src/Main.java
+++ b/test/593-checker-shift-and-simplifier/src/Main.java
@@ -21,6 +21,17 @@
// A very particular set of operations that caused a double removal by the
// ARM64 simplifier doing "forward" removals (b/27851582).
+ /// CHECK-START-ARM: int Main.operations() instruction_simplifier_arm (before)
+ /// CHECK-DAG: <<Get:i\d+>> ArrayGet
+ /// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>]
+ /// CHECK-DAG: <<Shl:i\d+>> Shl [<<Get>>,i{{\d+}}]
+ /// CHECK-DAG: And [<<Not>>,<<Shl>>]
+ //
+ /// CHECK-START-ARM: int Main.operations() instruction_simplifier_arm (after)
+ /// CHECK-DAG: <<Get:i\d+>> ArrayGet
+ /// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>]
+ /// CHECK-DAG: DataProcWithShifterOp [<<Not>>,<<Get>>] kind:And+LSL shift:2
+
/// CHECK-START-ARM64: int Main.operations() instruction_simplifier_arm64 (before)
/// CHECK-DAG: <<Get:i\d+>> ArrayGet
/// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>]
@@ -30,7 +41,7 @@
/// CHECK-START-ARM64: int Main.operations() instruction_simplifier_arm64 (after)
/// CHECK-DAG: <<Get:i\d+>> ArrayGet
/// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>]
- /// CHECK-DAG: Arm64DataProcWithShifterOp [<<Not>>,<<Get>>] kind:And+LSL shift:2
+ /// CHECK-DAG: DataProcWithShifterOp [<<Not>>,<<Get>>] kind:And+LSL shift:2
private static int operations() {
int r = a[0];
int n = ~r;
diff --git a/test/626-const-class-linking/clear_dex_cache_types.cc b/test/626-const-class-linking/clear_dex_cache_types.cc
index b35dff4..e1af02e 100644
--- a/test/626-const-class-linking/clear_dex_cache_types.cc
+++ b/test/626-const-class-linking/clear_dex_cache_types.cc
@@ -27,7 +27,8 @@
ScopedObjectAccess soa(Thread::Current());
mirror::DexCache* dex_cache = soa.Decode<mirror::Class>(cls)->GetDexCache();
for (size_t i = 0, num_types = dex_cache->NumResolvedTypes(); i != num_types; ++i) {
- dex_cache->SetResolvedType(dex::TypeIndex(i), ObjPtr<mirror::Class>(nullptr));
+ mirror::TypeDexCachePair cleared(nullptr, mirror::TypeDexCachePair::InvalidIndexForSlot(i));
+ dex_cache->GetResolvedTypes()[i].store(cleared, std::memory_order_relaxed);
}
}
diff --git a/test/638-no-line-number/build b/test/638-no-line-number/build
new file mode 100644
index 0000000..7eaf50e
--- /dev/null
+++ b/test/638-no-line-number/build
@@ -0,0 +1,25 @@
+#!/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
+
+mkdir classes
+# Only keep the source name, to make sure we do remove it in the stack trace
+# when there is no line number mapping.
+${JAVAC} -g:source -source 7 -target 7 -d classes `find src -name '*.java'`
+${DX} --dex --output=classes.dex classes
+zip $TEST_NAME.jar classes.dex
diff --git a/test/638-no-line-number/expected.txt b/test/638-no-line-number/expected.txt
new file mode 100644
index 0000000..ffde153
--- /dev/null
+++ b/test/638-no-line-number/expected.txt
@@ -0,0 +1,5 @@
+java.lang.Error
+ at Main.main(Unknown Source:2)
+java.lang.NullPointerException: throw with null exception
+ at Main.doThrow(Unknown Source:0)
+ at Main.main(Unknown Source:9)
diff --git a/test/638-no-line-number/info.txt b/test/638-no-line-number/info.txt
new file mode 100644
index 0000000..89e6432
--- /dev/null
+++ b/test/638-no-line-number/info.txt
@@ -0,0 +1 @@
+Test for b/30183883, that we emit the dex pc when the line number is missing.
diff --git a/test/638-no-line-number/src/Main.java b/test/638-no-line-number/src/Main.java
new file mode 100644
index 0000000..7fe0404
--- /dev/null
+++ b/test/638-no-line-number/src/Main.java
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+public class Main {
+ public static void main(String[] args) {
+ try {
+ doThrow(new Error());
+ } catch (Error e) {
+ e.printStackTrace();
+ }
+ try {
+ doThrow(null);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+
+ public static void doThrow(Error e) {
+ throw e;
+ }
+}
diff --git a/test/906-iterate-heap/expected.txt b/test/906-iterate-heap/expected.txt
index 72cd47d..c8228d6 100644
--- a/test/906-iterate-heap/expected.txt
+++ b/test/906-iterate-heap/expected.txt
@@ -1,2 +1,20 @@
-[{tag=1, class-tag=0, size=8, length=-1}, {tag=2, class-tag=100, size=8, length=-1}, {tag=3, class-tag=100, size=8, length=-1}, {tag=4, class-tag=0, size=32, length=5}, {tag=100, class-tag=0, size=<class>, length=-1}]
-[{tag=11, class-tag=0, size=8, length=-1}, {tag=12, class-tag=110, size=8, length=-1}, {tag=13, class-tag=110, size=8, length=-1}, {tag=14, class-tag=0, size=32, length=5}, {tag=110, class-tag=0, size=<class>, length=-1}]
+[{tag=1, class-tag=0, size=8, length=-1}, {tag=2, class-tag=100, size=8, length=-1}, {tag=3, class-tag=100, size=8, length=-1}, {tag=4, class-tag=0, size=32, length=5}, {tag=5, class-tag=0, size=40, length=-1}, {tag=100, class-tag=0, size=<class>, length=-1}]
+[{tag=11, class-tag=0, size=8, length=-1}, {tag=12, class-tag=110, size=8, length=-1}, {tag=13, class-tag=110, size=8, length=-1}, {tag=14, class-tag=0, size=32, length=5}, {tag=15, class-tag=0, size=40, length=-1}, {tag=110, class-tag=0, size=<class>, length=-1}]
+15@0 (40, 'Hello World')
+16
+1@0 (14, 2xZ '0001')
+2
+1@0 (15, 3xB '010203')
+2
+1@0 (16, 2xC '41005a00')
+2
+1@0 (18, 3xS '010002000300')
+2
+1@0 (24, 3xI '010000000200000003000000')
+2
+1@0 (20, 2xF '000000000000803f')
+2
+1@0 (40, 3xJ '010000000000000002000000000000000300000000000000')
+2
+1@0 (32, 2xD '0000000000000000000000000000f03f')
+2
diff --git a/test/906-iterate-heap/iterate_heap.cc b/test/906-iterate-heap/iterate_heap.cc
index 1362d47..890220e 100644
--- a/test/906-iterate-heap/iterate_heap.cc
+++ b/test/906-iterate-heap/iterate_heap.cc
@@ -14,17 +14,23 @@
* limitations under the License.
*/
+#include "inttypes.h"
+
+#include <iomanip>
#include <iostream>
#include <pthread.h>
+#include <sstream>
#include <stdio.h>
#include <vector>
+#include "android-base/stringprintf.h"
#include "base/logging.h"
#include "jni.h"
#include "openjdkjvmti/jvmti.h"
#include "ScopedPrimitiveArray.h"
#include "ti-agent/common_helper.h"
#include "ti-agent/common_load.h"
+#include "utf.h"
namespace art {
namespace Test906IterateHeap {
@@ -172,5 +178,149 @@
Run(heap_filter, klass_filter, &config);
}
+extern "C" JNIEXPORT jstring JNICALL Java_Main_iterateThroughHeapString(
+ JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jlong tag) {
+ struct FindStringCallbacks {
+ explicit FindStringCallbacks(jlong t) : tag_to_find(t) {}
+
+ static jint JNICALL HeapIterationCallback(jlong class_tag ATTRIBUTE_UNUSED,
+ jlong size ATTRIBUTE_UNUSED,
+ jlong* tag_ptr ATTRIBUTE_UNUSED,
+ jint length ATTRIBUTE_UNUSED,
+ void* user_data ATTRIBUTE_UNUSED) {
+ return 0;
+ }
+
+ static jint JNICALL StringValueCallback(jlong class_tag,
+ jlong size,
+ jlong* tag_ptr,
+ const jchar* value,
+ jint value_length,
+ void* user_data) {
+ FindStringCallbacks* p = reinterpret_cast<FindStringCallbacks*>(user_data);
+ if (*tag_ptr == p->tag_to_find) {
+ size_t utf_byte_count = CountUtf8Bytes(value, value_length);
+ std::unique_ptr<char[]> mod_utf(new char[utf_byte_count + 1]);
+ memset(mod_utf.get(), 0, utf_byte_count + 1);
+ ConvertUtf16ToModifiedUtf8(mod_utf.get(), utf_byte_count, value, value_length);
+ if (!p->data.empty()) {
+ p->data += "\n";
+ }
+ p->data += android::base::StringPrintf("%" PRId64 "@%" PRId64 " (%" PRId64 ", '%s')",
+ *tag_ptr,
+ class_tag,
+ size,
+ mod_utf.get());
+ // Update the tag to test whether that works.
+ *tag_ptr = *tag_ptr + 1;
+ }
+ return 0;
+ }
+
+ std::string data;
+ const jlong tag_to_find;
+ };
+
+ jvmtiHeapCallbacks callbacks;
+ memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks));
+ callbacks.heap_iteration_callback = FindStringCallbacks::HeapIterationCallback;
+ callbacks.string_primitive_value_callback = FindStringCallbacks::StringValueCallback;
+
+ FindStringCallbacks fsc(tag);
+ jvmtiError ret = jvmti_env->IterateThroughHeap(0, nullptr, &callbacks, &fsc);
+ if (JvmtiErrorToException(env, ret)) {
+ return nullptr;
+ }
+ return env->NewStringUTF(fsc.data.c_str());
+}
+
+extern "C" JNIEXPORT jstring JNICALL Java_Main_iterateThroughHeapPrimitiveArray(
+ JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jlong tag) {
+ struct FindArrayCallbacks {
+ explicit FindArrayCallbacks(jlong t) : tag_to_find(t) {}
+
+ static jint JNICALL HeapIterationCallback(jlong class_tag ATTRIBUTE_UNUSED,
+ jlong size ATTRIBUTE_UNUSED,
+ jlong* tag_ptr ATTRIBUTE_UNUSED,
+ jint length ATTRIBUTE_UNUSED,
+ void* user_data ATTRIBUTE_UNUSED) {
+ return 0;
+ }
+
+ static jint JNICALL ArrayValueCallback(jlong class_tag,
+ jlong size,
+ jlong* tag_ptr,
+ jint element_count,
+ jvmtiPrimitiveType element_type,
+ const void* elements,
+ void* user_data) {
+ FindArrayCallbacks* p = reinterpret_cast<FindArrayCallbacks*>(user_data);
+ if (*tag_ptr == p->tag_to_find) {
+ std::ostringstream oss;
+ oss << *tag_ptr
+ << '@'
+ << class_tag
+ << " ("
+ << size
+ << ", "
+ << element_count
+ << "x"
+ << static_cast<char>(element_type)
+ << " '";
+ size_t element_size;
+ switch (element_type) {
+ case JVMTI_PRIMITIVE_TYPE_BOOLEAN:
+ case JVMTI_PRIMITIVE_TYPE_BYTE:
+ element_size = 1;
+ break;
+ case JVMTI_PRIMITIVE_TYPE_CHAR:
+ case JVMTI_PRIMITIVE_TYPE_SHORT:
+ element_size = 2;
+ break;
+ case JVMTI_PRIMITIVE_TYPE_INT:
+ case JVMTI_PRIMITIVE_TYPE_FLOAT:
+ element_size = 4;
+ break;
+ case JVMTI_PRIMITIVE_TYPE_LONG:
+ case JVMTI_PRIMITIVE_TYPE_DOUBLE:
+ element_size = 8;
+ break;
+ default:
+ LOG(FATAL) << "Unknown type " << static_cast<size_t>(element_type);
+ UNREACHABLE();
+ }
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(elements);
+ for (size_t i = 0; i != element_size * element_count; ++i) {
+ oss << android::base::StringPrintf("%02x", data[i]);
+ }
+ oss << "')";
+
+ if (!p->data.empty()) {
+ p->data += "\n";
+ }
+ p->data += oss.str();
+ // Update the tag to test whether that works.
+ *tag_ptr = *tag_ptr + 1;
+ }
+ return 0;
+ }
+
+ std::string data;
+ const jlong tag_to_find;
+ };
+
+ jvmtiHeapCallbacks callbacks;
+ memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks));
+ callbacks.heap_iteration_callback = FindArrayCallbacks::HeapIterationCallback;
+ callbacks.array_primitive_value_callback = FindArrayCallbacks::ArrayValueCallback;
+
+ FindArrayCallbacks fac(tag);
+ jvmtiError ret = jvmti_env->IterateThroughHeap(0, nullptr, &callbacks, &fac);
+ if (JvmtiErrorToException(env, ret)) {
+ return nullptr;
+ }
+ return env->NewStringUTF(fac.data.c_str());
+}
+
} // namespace Test906IterateHeap
} // namespace art
diff --git a/test/906-iterate-heap/src/Main.java b/test/906-iterate-heap/src/Main.java
index cab27be..d499886 100644
--- a/test/906-iterate-heap/src/Main.java
+++ b/test/906-iterate-heap/src/Main.java
@@ -28,11 +28,13 @@
B b2 = new B();
C c = new C();
A[] aArray = new A[5];
+ String s = "Hello World";
setTag(a, 1);
setTag(b, 2);
setTag(b2, 3);
setTag(aArray, 4);
+ setTag(s, 5);
setTag(B.class, 100);
int all = iterateThroughHeapCount(0, null, Integer.MAX_VALUE);
@@ -50,7 +52,7 @@
throw new IllegalStateException("By class: " + all + " != " + taggedClass + " + " +
untaggedClass);
}
- if (tagged != 5) {
+ if (tagged != 6) {
throw new IllegalStateException(tagged + " tagged objects");
}
if (taggedClass != 2) {
@@ -74,6 +76,49 @@
iterateThroughHeapAdd(HEAP_FILTER_OUT_UNTAGGED, null);
n = iterateThroughHeapData(HEAP_FILTER_OUT_UNTAGGED, null, classTags, sizes, tags, lengths);
System.out.println(sort(n, classTags, sizes, tags, lengths));
+
+ System.out.println(iterateThroughHeapString(getTag(s)));
+ System.out.println(getTag(s));
+
+ boolean[] zArray = new boolean[] { false, true };
+ setTag(zArray, 1);
+ System.out.println(iterateThroughHeapPrimitiveArray(getTag(zArray)));
+ System.out.println(getTag(zArray));
+
+ byte[] bArray = new byte[] { 1, 2, 3 };
+ setTag(bArray, 1);
+ System.out.println(iterateThroughHeapPrimitiveArray(getTag(bArray)));
+ System.out.println(getTag(bArray));
+
+ char[] cArray = new char[] { 'A', 'Z' };
+ setTag(cArray, 1);
+ System.out.println(iterateThroughHeapPrimitiveArray(getTag(cArray)));
+ System.out.println(getTag(cArray));
+
+ short[] sArray = new short[] { 1, 2, 3 };
+ setTag(sArray, 1);
+ System.out.println(iterateThroughHeapPrimitiveArray(getTag(sArray)));
+ System.out.println(getTag(sArray));
+
+ int[] iArray = new int[] { 1, 2, 3 };
+ setTag(iArray, 1);
+ System.out.println(iterateThroughHeapPrimitiveArray(getTag(iArray)));
+ System.out.println(getTag(iArray));
+
+ float[] fArray = new float[] { 0.0f, 1.0f };
+ setTag(fArray, 1);
+ System.out.println(iterateThroughHeapPrimitiveArray(getTag(fArray)));
+ System.out.println(getTag(fArray));
+
+ long[] lArray = new long[] { 1, 2, 3 };
+ setTag(lArray, 1);
+ System.out.println(iterateThroughHeapPrimitiveArray(getTag(lArray)));
+ System.out.println(getTag(lArray));
+
+ double[] dArray = new double[] { 0.0, 1.0 };
+ setTag(dArray, 1);
+ System.out.println(iterateThroughHeapPrimitiveArray(getTag(dArray)));
+ System.out.println(getTag(dArray));
}
static class A {
@@ -141,4 +186,6 @@
Class<?> klassFilter, long classTags[], long sizes[], long tags[], int lengths[]);
private static native int iterateThroughHeapAdd(int heapFilter,
Class<?> klassFilter);
+ private static native String iterateThroughHeapString(long tag);
+ private static native String iterateThroughHeapPrimitiveArray(long tag);
}
diff --git a/test/909-attach-agent/attach.cc b/test/909-attach-agent/attach.cc
index 2b50eb8..adae844 100644
--- a/test/909-attach-agent/attach.cc
+++ b/test/909-attach-agent/attach.cc
@@ -28,7 +28,7 @@
jint OnAttach(JavaVM* vm,
char* options ATTRIBUTE_UNUSED,
void* reserved ATTRIBUTE_UNUSED) {
- printf("Attached Agent for test 909-attach-agent\n");
+ fprintf(stderr, "Attached Agent for test 909-attach-agent\n");
fsync(1);
jvmtiEnv* env = nullptr;
jvmtiEnv* env2 = nullptr;
@@ -36,7 +36,7 @@
#define CHECK_CALL_SUCCESS(c) \
do { \
if ((c) != JNI_OK) { \
- printf("call " #c " did not succeed\n"); \
+ fprintf(stderr, "call " #c " did not succeed\n"); \
return -1; \
} \
} while (false)
@@ -44,7 +44,7 @@
CHECK_CALL_SUCCESS(vm->GetEnv(reinterpret_cast<void**>(&env), JVMTI_VERSION_1_0));
CHECK_CALL_SUCCESS(vm->GetEnv(reinterpret_cast<void**>(&env2), JVMTI_VERSION_1_0));
if (env == env2) {
- printf("GetEnv returned same environment twice!\n");
+ fprintf(stderr, "GetEnv returned same environment twice!\n");
return -1;
}
unsigned char* local_data = nullptr;
@@ -54,19 +54,19 @@
unsigned char* get_data = nullptr;
CHECK_CALL_SUCCESS(env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&get_data)));
if (get_data != local_data) {
- printf("Got different data from local storage then what was set!\n");
+ fprintf(stderr, "Got different data from local storage then what was set!\n");
return -1;
}
CHECK_CALL_SUCCESS(env2->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&get_data)));
if (get_data != nullptr) {
- printf("env2 did not have nullptr local storage.\n");
+ fprintf(stderr, "env2 did not have nullptr local storage.\n");
return -1;
}
CHECK_CALL_SUCCESS(env->Deallocate(local_data));
jint version = 0;
CHECK_CALL_SUCCESS(env->GetVersionNumber(&version));
if ((version & JVMTI_VERSION_1) != JVMTI_VERSION_1) {
- printf("Unexpected version number!\n");
+ fprintf(stderr, "Unexpected version number!\n");
return -1;
}
CHECK_CALL_SUCCESS(env->DisposeEnvironment());
diff --git a/test/912-classes/expected.txt b/test/912-classes/expected.txt
index e932b20..6b86ac9 100644
--- a/test/912-classes/expected.txt
+++ b/test/912-classes/expected.txt
@@ -15,7 +15,8 @@
int interface=false array=false modifiable=false
$Proxy0 interface=false array=false modifiable=false
java.lang.Runnable interface=true array=false modifiable=false
-java.lang.String interface=false array=false modifiable=true
+java.lang.String interface=false array=false modifiable=false
+java.util.ArrayList interface=false array=false modifiable=true
[I interface=false array=true modifiable=false
[Ljava.lang.Runnable; interface=false array=true modifiable=false
[Ljava.lang.String; interface=false array=true modifiable=false
diff --git a/test/912-classes/src/Main.java b/test/912-classes/src/Main.java
index 005074f..5d25d76 100644
--- a/test/912-classes/src/Main.java
+++ b/test/912-classes/src/Main.java
@@ -17,6 +17,7 @@
import java.lang.ref.Reference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -40,6 +41,7 @@
testClassType(getProxyClass());
testClassType(Runnable.class);
testClassType(String.class);
+ testClassType(ArrayList.class);
testClassType(int[].class);
testClassType(Runnable[].class);
diff --git a/test/913-heaps/expected.txt b/test/913-heaps/expected.txt
index 7522a65..6432172 100644
--- a/test/913-heaps/expected.txt
+++ b/test/913-heaps/expected.txt
@@ -1,6 +1,6 @@
---
true true
-root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=11,location= 31])--> 1@1000 [size=16, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=132, length=-1]
root@root --(thread)--> 3000@0 [size=132, length=-1]
0@0 --(array-element@0)--> 1@1000 [size=16, length=-1]
@@ -40,9 +40,9 @@
---
root@root --(jni-global)--> 1@1000 [size=16, length=-1]
root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=10,location= 6])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 6])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=3,location= 18])--> 1@1000 [size=16, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=4,location= 19])--> 1@1000 [size=16, length=-1]
root@root --(thread)--> 1@1000 [size=16, length=-1]
root@root --(thread)--> 3000@0 [size=132, length=-1]
1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
@@ -79,3 +79,14 @@
5@1002 --(field@28)--> 1@1000 [size=16, length=-1]
6@1000 --(class)--> 1000@0 [size=123, length=-1]
---
+[1@0 (40, 'HelloWorld')]
+2
+2@0 (15, 3xB '010203')
+3@0 (16, 2xC '41005a00')
+8@0 (32, 2xD '0000000000000000000000000000f03f')
+6@0 (20, 2xF '000000000000803f')
+5@0 (24, 3xI '010000000200000003000000')
+7@0 (40, 3xJ '010000000000000002000000000000000300000000000000')
+4@0 (18, 3xS '010002000300')
+1@0 (14, 2xZ '0001')
+23456789
diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc
index 6759919..1de1a69 100644
--- a/test/913-heaps/heaps.cc
+++ b/test/913-heaps/heaps.cc
@@ -493,5 +493,158 @@
return ret;
}
+extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_followReferencesString(
+ JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject initial_object) {
+ struct FindStringCallbacks {
+ static jint JNICALL FollowReferencesCallback(
+ jvmtiHeapReferenceKind reference_kind ATTRIBUTE_UNUSED,
+ const jvmtiHeapReferenceInfo* reference_info ATTRIBUTE_UNUSED,
+ jlong class_tag ATTRIBUTE_UNUSED,
+ jlong referrer_class_tag ATTRIBUTE_UNUSED,
+ jlong size ATTRIBUTE_UNUSED,
+ jlong* tag_ptr ATTRIBUTE_UNUSED,
+ jlong* referrer_tag_ptr ATTRIBUTE_UNUSED,
+ jint length ATTRIBUTE_UNUSED,
+ void* user_data ATTRIBUTE_UNUSED) {
+ return JVMTI_VISIT_OBJECTS; // Continue visiting.
+ }
+
+ static jint JNICALL StringValueCallback(jlong class_tag,
+ jlong size,
+ jlong* tag_ptr,
+ const jchar* value,
+ jint value_length,
+ void* user_data) {
+ FindStringCallbacks* p = reinterpret_cast<FindStringCallbacks*>(user_data);
+ if (*tag_ptr != 0) {
+ size_t utf_byte_count = CountUtf8Bytes(value, value_length);
+ std::unique_ptr<char[]> mod_utf(new char[utf_byte_count + 1]);
+ memset(mod_utf.get(), 0, utf_byte_count + 1);
+ ConvertUtf16ToModifiedUtf8(mod_utf.get(), utf_byte_count, value, value_length);
+ p->data.push_back(android::base::StringPrintf("%" PRId64 "@%" PRId64 " (%" PRId64 ", '%s')",
+ *tag_ptr,
+ class_tag,
+ size,
+ mod_utf.get()));
+ // Update the tag to test whether that works.
+ *tag_ptr = *tag_ptr + 1;
+ }
+ return 0;
+ }
+
+ std::vector<std::string> data;
+ };
+
+ jvmtiHeapCallbacks callbacks;
+ memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks));
+ callbacks.heap_reference_callback = FindStringCallbacks::FollowReferencesCallback;
+ callbacks.string_primitive_value_callback = FindStringCallbacks::StringValueCallback;
+
+ FindStringCallbacks fsc;
+ jvmtiError ret = jvmti_env->FollowReferences(0, nullptr, initial_object, &callbacks, &fsc);
+ if (JvmtiErrorToException(env, ret)) {
+ return nullptr;
+ }
+
+ jobjectArray retArray = CreateObjectArray(env,
+ static_cast<jint>(fsc.data.size()),
+ "java/lang/String",
+ [&](jint i) {
+ return env->NewStringUTF(fsc.data[i].c_str());
+ });
+ return retArray;
+}
+
+
+extern "C" JNIEXPORT jstring JNICALL Java_Main_followReferencesPrimitiveArray(
+ JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject initial_object) {
+ struct FindArrayCallbacks {
+ static jint JNICALL FollowReferencesCallback(
+ jvmtiHeapReferenceKind reference_kind ATTRIBUTE_UNUSED,
+ const jvmtiHeapReferenceInfo* reference_info ATTRIBUTE_UNUSED,
+ jlong class_tag ATTRIBUTE_UNUSED,
+ jlong referrer_class_tag ATTRIBUTE_UNUSED,
+ jlong size ATTRIBUTE_UNUSED,
+ jlong* tag_ptr ATTRIBUTE_UNUSED,
+ jlong* referrer_tag_ptr ATTRIBUTE_UNUSED,
+ jint length ATTRIBUTE_UNUSED,
+ void* user_data ATTRIBUTE_UNUSED) {
+ return JVMTI_VISIT_OBJECTS; // Continue visiting.
+ }
+
+ static jint JNICALL ArrayValueCallback(jlong class_tag,
+ jlong size,
+ jlong* tag_ptr,
+ jint element_count,
+ jvmtiPrimitiveType element_type,
+ const void* elements,
+ void* user_data) {
+ FindArrayCallbacks* p = reinterpret_cast<FindArrayCallbacks*>(user_data);
+ if (*tag_ptr != 0) {
+ std::ostringstream oss;
+ oss << *tag_ptr
+ << '@'
+ << class_tag
+ << " ("
+ << size
+ << ", "
+ << element_count
+ << "x"
+ << static_cast<char>(element_type)
+ << " '";
+ size_t element_size;
+ switch (element_type) {
+ case JVMTI_PRIMITIVE_TYPE_BOOLEAN:
+ case JVMTI_PRIMITIVE_TYPE_BYTE:
+ element_size = 1;
+ break;
+ case JVMTI_PRIMITIVE_TYPE_CHAR:
+ case JVMTI_PRIMITIVE_TYPE_SHORT:
+ element_size = 2;
+ break;
+ case JVMTI_PRIMITIVE_TYPE_INT:
+ case JVMTI_PRIMITIVE_TYPE_FLOAT:
+ element_size = 4;
+ break;
+ case JVMTI_PRIMITIVE_TYPE_LONG:
+ case JVMTI_PRIMITIVE_TYPE_DOUBLE:
+ element_size = 8;
+ break;
+ default:
+ LOG(FATAL) << "Unknown type " << static_cast<size_t>(element_type);
+ UNREACHABLE();
+ }
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(elements);
+ for (size_t i = 0; i != element_size * element_count; ++i) {
+ oss << android::base::StringPrintf("%02x", data[i]);
+ }
+ oss << "')";
+
+ if (!p->data.empty()) {
+ p->data += "\n";
+ }
+ p->data += oss.str();
+ // Update the tag to test whether that works.
+ *tag_ptr = *tag_ptr + 1;
+ }
+ return 0;
+ }
+
+ std::string data;
+ };
+
+ jvmtiHeapCallbacks callbacks;
+ memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks));
+ callbacks.heap_reference_callback = FindArrayCallbacks::FollowReferencesCallback;
+ callbacks.array_primitive_value_callback = FindArrayCallbacks::ArrayValueCallback;
+
+ FindArrayCallbacks fac;
+ jvmtiError ret = jvmti_env->FollowReferences(0, nullptr, initial_object, &callbacks, &fac);
+ if (JvmtiErrorToException(env, ret)) {
+ return nullptr;
+ }
+ return env->NewStringUTF(fac.data.c_str());
+}
+
} // namespace Test913Heaps
} // namespace art
diff --git a/test/913-heaps/src/Main.java b/test/913-heaps/src/Main.java
index 5a11a5b..2767d89 100644
--- a/test/913-heaps/src/Main.java
+++ b/test/913-heaps/src/Main.java
@@ -15,6 +15,7 @@
*/
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -22,7 +23,10 @@
public class Main {
public static void main(String[] args) throws Exception {
doTest();
- doFollowReferencesTest();
+ new TestConfig().doFollowReferencesTest();
+
+ doStringTest();
+ doPrimitiveArrayTest();
}
public static void doTest() throws Exception {
@@ -33,6 +37,64 @@
enableGcTracking(false);
}
+ public static void doStringTest() throws Exception {
+ final String str = "HelloWorld";
+ Object o = new Object() {
+ String s = str;
+ };
+
+ setTag(str, 1);
+ System.out.println(Arrays.toString(followReferencesString(o)));
+ System.out.println(getTag(str));
+ }
+
+ public static void doPrimitiveArrayTest() throws Exception {
+ final boolean[] zArray = new boolean[] { false, true };
+ setTag(zArray, 1);
+
+ final byte[] bArray = new byte[] { 1, 2, 3 };
+ setTag(bArray, 2);
+
+ final char[] cArray = new char[] { 'A', 'Z' };
+ setTag(cArray, 3);
+
+ final short[] sArray = new short[] { 1, 2, 3 };
+ setTag(sArray, 4);
+
+ final int[] iArray = new int[] { 1, 2, 3 };
+ setTag(iArray, 5);
+
+ final float[] fArray = new float[] { 0.0f, 1.0f };
+ setTag(fArray, 6);
+
+ final long[] lArray = new long[] { 1, 2, 3 };
+ setTag(lArray, 7);
+
+ final double[] dArray = new double[] { 0.0, 1.0 };
+ setTag(dArray, 8);
+
+ Object o = new Object() {
+ Object z = zArray;
+ Object b = bArray;
+ Object c = cArray;
+ Object s = sArray;
+ Object i = iArray;
+ Object f = fArray;
+ Object l = lArray;
+ Object d = dArray;
+ };
+
+ System.out.println(followReferencesPrimitiveArray(o));
+ System.out.print(getTag(zArray));
+ System.out.print(getTag(bArray));
+ System.out.print(getTag(cArray));
+ System.out.print(getTag(sArray));
+ System.out.print(getTag(iArray));
+ System.out.print(getTag(fArray));
+ System.out.print(getTag(lArray));
+ System.out.println(getTag(dArray));
+ }
+
private static void run() {
clearStats();
forceGarbageCollection();
@@ -51,126 +113,136 @@
System.out.println((s > 0) + " " + (f > 0));
}
- public static void doFollowReferencesTest() throws Exception {
- // Force GCs to clean up dirt.
- Runtime.getRuntime().gc();
- Runtime.getRuntime().gc();
+ private static class TestConfig {
+ private Class<?> klass = null;
+ private int heapFilter = 0;
- setTag(Thread.currentThread(), 3000);
-
- {
- ArrayList<Object> tmpStorage = new ArrayList<>();
- doFollowReferencesTestNonRoot(tmpStorage);
- tmpStorage = null;
+ public TestConfig() {
+ }
+ public TestConfig(Class<?> klass, int heapFilter) {
+ this.klass = klass;
+ this.heapFilter = heapFilter;
}
- // Force GCs to clean up dirt.
- Runtime.getRuntime().gc();
- Runtime.getRuntime().gc();
+ public void doFollowReferencesTest() throws Exception {
+ // Force GCs to clean up dirt.
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().gc();
- doFollowReferencesTestRoot();
+ setTag(Thread.currentThread(), 3000);
- // Force GCs to clean up dirt.
- Runtime.getRuntime().gc();
- Runtime.getRuntime().gc();
- }
+ {
+ ArrayList<Object> tmpStorage = new ArrayList<>();
+ doFollowReferencesTestNonRoot(tmpStorage);
+ tmpStorage = null;
+ }
- private static void doFollowReferencesTestNonRoot(ArrayList<Object> tmpStorage) {
- Verifier v = new Verifier();
- tagClasses(v);
- A a = createTree(v);
- tmpStorage.add(a);
- v.add("0@0", "1@1000"); // tmpStorage[0] --(array-element)--> a.
+ // Force GCs to clean up dirt.
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().gc();
- doFollowReferencesTestImpl(null, Integer.MAX_VALUE, -1, null, v, null);
- doFollowReferencesTestImpl(a.foo2, Integer.MAX_VALUE, -1, null, v, "3@1001");
+ doFollowReferencesTestRoot();
- tmpStorage.clear();
- }
+ // Force GCs to clean up dirt.
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().gc();
+ }
- private static void doFollowReferencesTestRoot() {
- Verifier v = new Verifier();
- tagClasses(v);
- A a = createTree(v);
+ private void doFollowReferencesTestNonRoot(ArrayList<Object> tmpStorage) {
+ Verifier v = new Verifier();
+ tagClasses(v);
+ A a = createTree(v);
+ tmpStorage.add(a);
+ v.add("0@0", "1@1000"); // tmpStorage[0] --(array-element)--> a.
- doFollowReferencesTestImpl(null, Integer.MAX_VALUE, -1, a, v, null);
- doFollowReferencesTestImpl(a.foo2, Integer.MAX_VALUE, -1, a, v, "3@1001");
- }
+ doFollowReferencesTestImpl(null, Integer.MAX_VALUE, -1, null, v, null);
+ doFollowReferencesTestImpl(a.foo2, Integer.MAX_VALUE, -1, null, v, "3@1001");
- private static void doFollowReferencesTestImpl(A root, int stopAfter, int followSet,
- Object asRoot, Verifier v, String additionalEnabled) {
- String[] lines =
- followReferences(0, null, root, stopAfter, followSet, asRoot);
+ tmpStorage.clear();
+ }
- v.process(lines, additionalEnabled);
+ private void doFollowReferencesTestRoot() {
+ Verifier v = new Verifier();
+ tagClasses(v);
+ A a = createTree(v);
- // TODO: Test filters.
- }
+ doFollowReferencesTestImpl(null, Integer.MAX_VALUE, -1, a, v, null);
+ doFollowReferencesTestImpl(a.foo2, Integer.MAX_VALUE, -1, a, v, "3@1001");
+ }
- private static void tagClasses(Verifier v) {
- setTag(A.class, 1000);
+ private void doFollowReferencesTestImpl(A root, int stopAfter, int followSet,
+ Object asRoot, Verifier v, String additionalEnabled) {
+ String[] lines =
+ followReferences(heapFilter, klass, root, stopAfter, followSet, asRoot);
- setTag(B.class, 1001);
- v.add("1001@0", "1000@0"); // B.class --(superclass)--> A.class.
+ v.process(lines, additionalEnabled, heapFilter != 0 || klass != null);
+ }
- setTag(C.class, 1002);
- v.add("1002@0", "1001@0"); // C.class --(superclass)--> B.class.
- v.add("1002@0", "2001@0"); // C.class --(interface)--> I2.class.
+ private static void tagClasses(Verifier v) {
+ setTag(A.class, 1000);
- setTag(I1.class, 2000);
+ setTag(B.class, 1001);
+ v.add("1001@0", "1000@0"); // B.class --(superclass)--> A.class.
- setTag(I2.class, 2001);
- v.add("2001@0", "2000@0"); // I2.class --(interface)--> I1.class.
- }
+ setTag(C.class, 1002);
+ v.add("1002@0", "1001@0"); // C.class --(superclass)--> B.class.
+ v.add("1002@0", "2001@0"); // C.class --(interface)--> I2.class.
- private static A createTree(Verifier v) {
- A aInst = new A();
- setTag(aInst, 1);
- String aInstStr = "1@1000";
- String aClassStr = "1000@0";
- v.add(aInstStr, aClassStr); // A -->(class) --> A.class.
+ setTag(I1.class, 2000);
- A a2Inst = new A();
- setTag(a2Inst, 2);
- aInst.foo = a2Inst;
- String a2InstStr = "2@1000";
- v.add(a2InstStr, aClassStr); // A2 -->(class) --> A.class.
- v.add(aInstStr, a2InstStr); // A -->(field) --> A2.
+ setTag(I2.class, 2001);
+ v.add("2001@0", "2000@0"); // I2.class --(interface)--> I1.class.
+ }
- B bInst = new B();
- setTag(bInst, 3);
- aInst.foo2 = bInst;
- String bInstStr = "3@1001";
- String bClassStr = "1001@0";
- v.add(bInstStr, bClassStr); // B -->(class) --> B.class.
- v.add(aInstStr, bInstStr); // A -->(field) --> B.
+ private static A createTree(Verifier v) {
+ A aInst = new A();
+ setTag(aInst, 1);
+ String aInstStr = "1@1000";
+ String aClassStr = "1000@0";
+ v.add(aInstStr, aClassStr); // A -->(class) --> A.class.
- A a3Inst = new A();
- setTag(a3Inst, 4);
- bInst.bar = a3Inst;
- String a3InstStr = "4@1000";
- v.add(a3InstStr, aClassStr); // A3 -->(class) --> A.class.
- v.add(bInstStr, a3InstStr); // B -->(field) --> A3.
+ A a2Inst = new A();
+ setTag(a2Inst, 2);
+ aInst.foo = a2Inst;
+ String a2InstStr = "2@1000";
+ v.add(a2InstStr, aClassStr); // A2 -->(class) --> A.class.
+ v.add(aInstStr, a2InstStr); // A -->(field) --> A2.
- C cInst = new C();
- setTag(cInst, 5);
- bInst.bar2 = cInst;
- String cInstStr = "5@1000";
- String cClassStr = "1002@0";
- v.add(cInstStr, cClassStr); // C -->(class) --> C.class.
- v.add(bInstStr, cInstStr); // B -->(field) --> C.
+ B bInst = new B();
+ setTag(bInst, 3);
+ aInst.foo2 = bInst;
+ String bInstStr = "3@1001";
+ String bClassStr = "1001@0";
+ v.add(bInstStr, bClassStr); // B -->(class) --> B.class.
+ v.add(aInstStr, bInstStr); // A -->(field) --> B.
- A a4Inst = new A();
- setTag(a4Inst, 6);
- cInst.baz = a4Inst;
- String a4InstStr = "6@1000";
- v.add(a4InstStr, aClassStr); // A4 -->(class) --> A.class.
- v.add(cInstStr, a4InstStr); // C -->(field) --> A4.
+ A a3Inst = new A();
+ setTag(a3Inst, 4);
+ bInst.bar = a3Inst;
+ String a3InstStr = "4@1000";
+ v.add(a3InstStr, aClassStr); // A3 -->(class) --> A.class.
+ v.add(bInstStr, a3InstStr); // B -->(field) --> A3.
- cInst.baz2 = aInst;
- v.add(cInstStr, aInstStr); // C -->(field) --> A.
+ C cInst = new C();
+ setTag(cInst, 5);
+ bInst.bar2 = cInst;
+ String cInstStr = "5@1000";
+ String cClassStr = "1002@0";
+ v.add(cInstStr, cClassStr); // C -->(class) --> C.class.
+ v.add(bInstStr, cInstStr); // B -->(field) --> C.
- return aInst;
+ A a4Inst = new A();
+ setTag(a4Inst, 6);
+ cInst.baz = a4Inst;
+ String a4InstStr = "6@1000";
+ v.add(a4InstStr, aClassStr); // A4 -->(class) --> A.class.
+ v.add(cInstStr, a4InstStr); // C -->(field) --> A4.
+
+ cInst.baz2 = aInst;
+ v.add(cInstStr, aInstStr); // C -->(field) --> A.
+
+ return aInst;
+ }
}
public static class A {
@@ -243,7 +315,7 @@
}
}
- public void process(String[] lines, String additionalEnabledReferrer) {
+ public void process(String[] lines, String additionalEnabledReferrer, boolean filtered) {
// This method isn't optimal. The loops could be merged. However, it's more readable if
// the different parts are separated.
@@ -303,6 +375,21 @@
}
}
+ if (filtered) {
+ // If we aren't tracking dependencies, just sort the lines and print.
+ // TODO: As the verifier is currently using the output lines to track dependencies, we
+ // cannot verify that output is correct when parts of it are suppressed by filters.
+ // To correctly track this we need to take node information into account, and
+ // actually analyze the graph.
+ Collections.sort(nonRootLines);
+ for (String l : nonRootLines) {
+ System.out.println(l);
+ }
+
+ System.out.println("---");
+ return;
+ }
+
// Iterate through the lines, keeping track of which referrers are visited, to ensure the
// order is acceptable.
HashSet<String> enabled = new HashSet<>();
@@ -379,9 +466,11 @@
private static native int getGcFinishes();
private static native void forceGarbageCollection();
- private static native void setTag(Object o, long tag);
- private static native long getTag(Object o);
+ public static native void setTag(Object o, long tag);
+ public static native long getTag(Object o);
- private static native String[] followReferences(int heapFilter, Class<?> klassFilter,
+ public static native String[] followReferences(int heapFilter, Class<?> klassFilter,
Object initialObject, int stopAfter, int followSet, Object jniRef);
+ public static native String[] followReferencesString(Object initialObject);
+ public static native String followReferencesPrimitiveArray(Object initialObject);
}
diff --git a/test/946-obsolete-throw/src/Main.java b/test/946-obsolete-throw/src/Main.java
index 3ff97ae..21fe972 100644
--- a/test/946-obsolete-throw/src/Main.java
+++ b/test/946-obsolete-throw/src/Main.java
@@ -71,7 +71,7 @@
t.sayHi(new DoRedefinitionClass());
} catch (Throwable e) {
System.out.println("Received error : " + e);
- e.printStackTrace();
+ e.printStackTrace(System.out);
}
t.sayHi(() -> { System.out.println("Not doing anything here"); });
}
diff --git a/test/950-redefine-intrinsic/expected.txt b/test/950-redefine-intrinsic/expected.txt
new file mode 100644
index 0000000..1264c94
--- /dev/null
+++ b/test/950-redefine-intrinsic/expected.txt
@@ -0,0 +1 @@
+Finished!
diff --git a/test/950-redefine-intrinsic/info.txt b/test/950-redefine-intrinsic/info.txt
new file mode 100644
index 0000000..c19d2b4
--- /dev/null
+++ b/test/950-redefine-intrinsic/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that we are able to redefine intrinsic functions.
diff --git a/test/950-redefine-intrinsic/run b/test/950-redefine-intrinsic/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/950-redefine-intrinsic/run
@@ -0,0 +1,17 @@
+#!/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-run "$@" --jvmti
diff --git a/test/950-redefine-intrinsic/src/Main.java b/test/950-redefine-intrinsic/src/Main.java
new file mode 100644
index 0000000..30cd3ab
--- /dev/null
+++ b/test/950-redefine-intrinsic/src/Main.java
@@ -0,0 +1,471 @@
+/*
+ * 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.
+ */
+
+import java.util.Base64;
+import java.util.Random;
+import java.util.function.*;
+import java.util.stream.*;
+
+public class Main {
+
+ // The bytes below define the following java program.
+ // package java.lang;
+ // import java.math.*;
+ // public final class Long extends Number implements Comparable<Long> {
+ // public static final long MIN_VALUE = 0;
+ // public static final long MAX_VALUE = 0;
+ // public static final Class<Long> TYPE = null;
+ // static { }
+ // // Used for Stream.count for some reason.
+ // public static long sum(long a, long b) {
+ // return a + b;
+ // }
+ // // Used in stream/lambda functions.
+ // public Long(long value) {
+ // this.value = value;
+ // }
+ // // Used in stream/lambda functions.
+ // public static Long valueOf(long l) {
+ // return new Long(l);
+ // }
+ // // Intrinsic! Do something cool. Return i + 1
+ // public static long highestOneBit(long i) {
+ // return i + 1;
+ // }
+ // // Intrinsic! Do something cool. Return i - 1
+ // public static long lowestOneBit(long i) {
+ // return i - 1;
+ // }
+ // // Intrinsic! Do something cool. Return i + i
+ // public static int numberOfLeadingZeros(long i) {
+ // return (int)(i + i);
+ // }
+ // // Intrinsic! Do something cool. Return i & (i >>> 1);
+ // public static int numberOfTrailingZeros(long i) {
+ // return (int)(i & (i >>> 1));
+ // }
+ // // Intrinsic! Do something cool. Return 5
+ // public static int bitCount(long i) {
+ // return 5;
+ // }
+ // // Intrinsic! Do something cool. Return i
+ // public static long rotateLeft(long i, int distance) {
+ // return i;
+ // }
+ // // Intrinsic! Do something cool. Return 10 * i
+ // public static long rotateRight(long i, int distance) {
+ // return 10 * i;
+ // }
+ // // Intrinsic! Do something cool. Return -i
+ // public static long reverse(long i) {
+ // return -i;
+ // }
+ // // Intrinsic! Do something cool. Return 0
+ // public static int signum(long i) {
+ // return 0;
+ // }
+ // // Intrinsic! Do something cool. Return 0
+ // public static long reverseBytes(long i) {
+ // return 0;
+ // }
+ // public String toString() {
+ // return "Redefined Long! value (as double)=" + ((double)value);
+ // }
+ // public static String toString(long i, int radix) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static String toUnsignedString(long i, int radix) {
+ // throw new Error("Method redefined away!");
+ // }
+ // private static BigInteger toUnsignedBigInteger(long i) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static String toHexString(long i) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static String toOctalString(long i) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static String toBinaryString(long i) {
+ // throw new Error("Method redefined away!");
+ // }
+ // static String toUnsignedString0(long val, int shift) {
+ // throw new Error("Method redefined away!");
+ // }
+ // static int formatUnsignedLong(long val, int shift, char[] buf, int offset, int len) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static String toString(long i) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static String toUnsignedString(long i) {
+ // throw new Error("Method redefined away!");
+ // }
+ // static void getChars(long i, int index, char[] buf) {
+ // throw new Error("Method redefined away!");
+ // }
+ // static int stringSize(long x) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static long parseLong(String s, int radix) throws NumberFormatException {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static long parseLong(String s) throws NumberFormatException {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static long parseUnsignedLong(String s, int radix) throws NumberFormatException {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static long parseUnsignedLong(String s) throws NumberFormatException {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static Long valueOf(String s, int radix) throws NumberFormatException {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static Long valueOf(String s) throws NumberFormatException {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static Long decode(String nm) throws NumberFormatException {
+ // throw new Error("Method redefined away!");
+ // }
+ // private final long value;
+ // public Long(String s) throws NumberFormatException {
+ // this(0);
+ // throw new Error("Method redefined away!");
+ // }
+ // public byte byteValue() {
+ // throw new Error("Method redefined away!");
+ // }
+ // public short shortValue() {
+ // throw new Error("Method redefined away!");
+ // }
+ // public int intValue() {
+ // throw new Error("Method redefined away!");
+ // }
+ // public long longValue() {
+ // return value;
+ // }
+ // public float floatValue() {
+ // throw new Error("Method redefined away!");
+ // }
+ // public double doubleValue() {
+ // throw new Error("Method redefined away!");
+ // }
+ // public int hashCode() {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static int hashCode(long value) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public boolean equals(Object obj) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static Long getLong(String nm) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static Long getLong(String nm, long val) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static Long getLong(String nm, Long val) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public int compareTo(Long anotherLong) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static int compare(long x, long y) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static int compareUnsigned(long x, long y) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static long divideUnsigned(long dividend, long divisor) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static long remainderUnsigned(long dividend, long divisor) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static final int SIZE = 64;
+ // public static final int BYTES = SIZE / Byte.SIZE;
+ // public static long max(long a, long b) {
+ // throw new Error("Method redefined away!");
+ // }
+ // public static long min(long a, long b) {
+ // throw new Error("Method redefined away!");
+ // }
+ // private static final long serialVersionUID = 0;
+ // }
+ private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+ "yv66vgAAADQAiQUAAAAAAAAACgcAdQoAAwB2CAB3CgADAHgJAA0AeQoAAwB6CgADAHsHAHwIAH0K" +
+ "AAoAfgcAfwoADQCACgASAHYKAA0AgQkADQCCBwCDBwCEAQAJTUlOX1ZBTFVFAQABSgEADUNvbnN0" +
+ "YW50VmFsdWUFAAAAAAAAAAABAAlNQVhfVkFMVUUBAARUWVBFAQARTGphdmEvbGFuZy9DbGFzczsB" +
+ "AAlTaWduYXR1cmUBACNMamF2YS9sYW5nL0NsYXNzPExqYXZhL2xhbmcvTG9uZzs+OwEABXZhbHVl" +
+ "AQAEU0laRQEAAUkDAAAAQAEABUJZVEVTAwAAAAgBABBzZXJpYWxWZXJzaW9uVUlEAQANaGlnaGVz" +
+ "dE9uZUJpdAEABChKKUoBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAMbG93ZXN0T25lQml0AQAU" +
+ "bnVtYmVyT2ZMZWFkaW5nWmVyb3MBAAQoSilJAQAVbnVtYmVyT2ZUcmFpbGluZ1plcm9zAQAIYml0" +
+ "Q291bnQBAApyb3RhdGVMZWZ0AQAFKEpJKUoBAAtyb3RhdGVSaWdodAEAB3JldmVyc2UBAAZzaWdu" +
+ "dW0BAAxyZXZlcnNlQnl0ZXMBAAh0b1N0cmluZwEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAWKEpJ" +
+ "KUxqYXZhL2xhbmcvU3RyaW5nOwEAEHRvVW5zaWduZWRTdHJpbmcBABR0b1Vuc2lnbmVkQmlnSW50" +
+ "ZWdlcgEAGShKKUxqYXZhL21hdGgvQmlnSW50ZWdlcjsBAAt0b0hleFN0cmluZwEAFShKKUxqYXZh" +
+ "L2xhbmcvU3RyaW5nOwEADXRvT2N0YWxTdHJpbmcBAA50b0JpbmFyeVN0cmluZwEAEXRvVW5zaWdu" +
+ "ZWRTdHJpbmcwAQASZm9ybWF0VW5zaWduZWRMb25nAQAJKEpJW0NJSSlJAQAIZ2V0Q2hhcnMBAAco" +
+ "SklbQylWAQAKc3RyaW5nU2l6ZQEACXBhcnNlTG9uZwEAFihMamF2YS9sYW5nL1N0cmluZztJKUoB" +
+ "AApFeGNlcHRpb25zBwCFAQAVKExqYXZhL2xhbmcvU3RyaW5nOylKAQARcGFyc2VVbnNpZ25lZExv" +
+ "bmcBAAd2YWx1ZU9mAQAlKExqYXZhL2xhbmcvU3RyaW5nO0kpTGphdmEvbGFuZy9Mb25nOwEAJChM" +
+ "amF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Mb25nOwEAEyhKKUxqYXZhL2xhbmcvTG9uZzsB" +
+ "AAZkZWNvZGUBAAY8aW5pdD4BAAQoSilWAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAJYnl0ZVZh" +
+ "bHVlAQADKClCAQAKc2hvcnRWYWx1ZQEAAygpUwEACGludFZhbHVlAQADKClJAQAJbG9uZ1ZhbHVl" +
+ "AQADKClKAQAKZmxvYXRWYWx1ZQEAAygpRgEAC2RvdWJsZVZhbHVlAQADKClEAQAIaGFzaENvZGUB" +
+ "AAZlcXVhbHMBABUoTGphdmEvbGFuZy9PYmplY3Q7KVoBAAdnZXRMb25nAQAlKExqYXZhL2xhbmcv" +
+ "U3RyaW5nO0opTGphdmEvbGFuZy9Mb25nOwEANChMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5n" +
+ "L0xvbmc7KUxqYXZhL2xhbmcvTG9uZzsBAAljb21wYXJlVG8BABMoTGphdmEvbGFuZy9Mb25nOylJ" +
+ "AQAHY29tcGFyZQEABShKSilJAQAPY29tcGFyZVVuc2lnbmVkAQAOZGl2aWRlVW5zaWduZWQBAAUo" +
+ "SkopSgEAEXJlbWFpbmRlclVuc2lnbmVkAQADc3VtAQADbWF4AQADbWluAQAVKExqYXZhL2xhbmcv" +
+ "T2JqZWN0OylJAQAIPGNsaW5pdD4BAAMoKVYBADpMamF2YS9sYW5nL051bWJlcjtMamF2YS9sYW5n" +
+ "L0NvbXBhcmFibGU8TGphdmEvbGFuZy9Mb25nOz47AQAKU291cmNlRmlsZQEACUxvbmcuamF2YQEA" +
+ "F2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyDABPAHEBACJSZWRlZmluZWQgTG9uZyEgdmFsdWUgKGFz" +
+ "IGRvdWJsZSk9DACGAIcMAB4AFQwAhgCIDAA0ADUBAA9qYXZhL2xhbmcvRXJyb3IBABZNZXRob2Qg" +
+ "cmVkZWZpbmVkIGF3YXkhDABPAFEBAA5qYXZhL2xhbmcvTG9uZwwATwBQDABkAGUMABoAGwEAEGph" +
+ "dmEvbGFuZy9OdW1iZXIBABRqYXZhL2xhbmcvQ29tcGFyYWJsZQEAH2phdmEvbGFuZy9OdW1iZXJG" +
+ "b3JtYXRFeGNlcHRpb24BAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcv" +
+ "U3RyaW5nQnVpbGRlcjsBABwoRClMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7ADEADQASAAEAEwAH" +
+ "ABkAFAAVAAEAFgAAAAIAFwAZABkAFQABABYAAAACABcAGQAaABsAAQAcAAAAAgAdABIAHgAVAAAA" +
+ "GQAfACAAAQAWAAAAAgAhABkAIgAgAAEAFgAAAAIAIwAaACQAFQABABYAAAACABcANwAJACUAJgAB" +
+ "ACcAAAAcAAQAAgAAAAQeCmGtAAAAAQAoAAAABgABAAAADgAJACkAJgABACcAAAAcAAQAAgAAAAQe" +
+ "CmWtAAAAAQAoAAAABgABAAAAEwAJACoAKwABACcAAAAdAAQAAgAAAAUeHmGIrAAAAAEAKAAAAAYA" +
+ "AQAAABgACQAsACsAAQAnAAAAHwAFAAIAAAAHHh4EfX+IrAAAAAEAKAAAAAYAAQAAAB0ACQAtACsA" +
+ "AQAnAAAAGgABAAIAAAACCKwAAAABACgAAAAGAAEAAAAiAAkALgAvAAEAJwAAABoAAgADAAAAAh6t" +
+ "AAAAAQAoAAAABgABAAAAJwAJADAALwABACcAAAAeAAQAAwAAAAYUAAEeaa0AAAABACgAAAAGAAEA" +
+ "AAAsAAkAMQAmAAEAJwAAABsAAgACAAAAAx51rQAAAAEAKAAAAAYAAQAAADEACQAyACsAAQAnAAAA" +
+ "GgABAAIAAAACA6wAAAABACgAAAAGAAEAAAA2AAkAMwAmAAEAJwAAABoAAgACAAAAAgmtAAAAAQAo" +
+ "AAAABgABAAAAOwABADQANQABACcAAAAwAAMAAQAAABi7AANZtwAEEgW2AAYqtAAHirYACLYACbAA" +
+ "AAABACgAAAAGAAEAAAA/AAkANAA2AAEAJwAAACIAAwADAAAACrsAClkSC7cADL8AAAABACgAAAAG" +
+ "AAEAAABDAAkANwA2AAEAJwAAACIAAwADAAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAABGAAoA" +
+ "OAA5AAEAJwAAACIAAwACAAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAABKAAkAOgA7AAEAJwAA" +
+ "ACIAAwACAAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAABNAAkAPAA7AAEAJwAAACIAAwACAAAA" +
+ "CrsAClkSC7cADL8AAAABACgAAAAGAAEAAABRAAkAPQA7AAEAJwAAACIAAwACAAAACrsAClkSC7cA" +
+ "DL8AAAABACgAAAAGAAEAAABVAAgAPgA2AAEAJwAAACIAAwADAAAACrsAClkSC7cADL8AAAABACgA" +
+ "AAAGAAEAAABZAAgAPwBAAAEAJwAAACIAAwAGAAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAABd" +
+ "AAkANAA7AAEAJwAAACIAAwACAAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAABhAAkANwA7AAEA" +
+ "JwAAACIAAwACAAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAABlAAgAQQBCAAEAJwAAACIAAwAE" +
+ "AAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAABpAAgAQwArAAEAJwAAACIAAwACAAAACrsAClkS" +
+ "C7cADL8AAAABACgAAAAGAAEAAABtAAkARABFAAIAJwAAACIAAwACAAAACrsAClkSC7cADL8AAAAB" +
+ "ACgAAAAGAAEAAABxAEYAAAAEAAEARwAJAEQASAACACcAAAAiAAMAAQAAAAq7AApZEgu3AAy/AAAA" +
+ "AQAoAAAABgABAAAAdQBGAAAABAABAEcACQBJAEUAAgAnAAAAIgADAAIAAAAKuwAKWRILtwAMvwAA" +
+ "AAEAKAAAAAYAAQAAAHkARgAAAAQAAQBHAAkASQBIAAIAJwAAACIAAwABAAAACrsAClkSC7cADL8A" +
+ "AAABACgAAAAGAAEAAAB9AEYAAAAEAAEARwAJAEoASwACACcAAAAiAAMAAgAAAAq7AApZEgu3AAy/" +
+ "AAAAAQAoAAAABgABAAAAgQBGAAAABAABAEcACQBKAEwAAgAnAAAAIgADAAEAAAAKuwAKWRILtwAM" +
+ "vwAAAAEAKAAAAAYAAQAAAIQARgAAAAQAAQBHAAkASgBNAAEAJwAAACEABAACAAAACbsADVketwAO" +
+ "sAAAAAEAKAAAAAYAAQAAAIcACQBOAEwAAgAnAAAAIgADAAEAAAAKuwAKWRILtwAMvwAAAAEAKAAA" +
+ "AAYAAQAAAIsARgAAAAQAAQBHAAEATwBQAAEAJwAAACoAAwADAAAACiq3AA8qH7UAB7EAAAABACgA" +
+ "AAAOAAMAAACQAAQAkQAJAJIAAQBPAFEAAgAnAAAAKwADAAIAAAAPKgm3AA67AApZEgu3AAy/AAAA" +
+ "AQAoAAAACgACAAAAlQAFAJYARgAAAAQAAQBHAAEAUgBTAAEAJwAAACIAAwABAAAACrsAClkSC7cA" +
+ "DL8AAAABACgAAAAGAAEAAACaAAEAVABVAAEAJwAAACIAAwABAAAACrsAClkSC7cADL8AAAABACgA" +
+ "AAAGAAEAAACeAAEAVgBXAAEAJwAAACIAAwABAAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAACi" +
+ "AAEAWABZAAEAJwAAAB0AAgABAAAABSq0AAetAAAAAQAoAAAABgABAAAApgABAFoAWwABACcAAAAi" +
+ "AAMAAQAAAAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAAqgABAFwAXQABACcAAAAiAAMAAQAAAAq7" +
+ "AApZEgu3AAy/AAAAAQAoAAAABgABAAAArgABAF4AVwABACcAAAAiAAMAAQAAAAq7AApZEgu3AAy/" +
+ "AAAAAQAoAAAABgABAAAAsgAJAF4AKwABACcAAAAiAAMAAgAAAAq7AApZEgu3AAy/AAAAAQAoAAAA" +
+ "BgABAAAAtgABAF8AYAABACcAAAAiAAMAAgAAAAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAAugAJ" +
+ "AGEATAABACcAAAAiAAMAAQAAAAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAAvgAJAGEAYgABACcA" +
+ "AAAiAAMAAwAAAAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAAwgAJAGEAYwABACcAAAAiAAMAAgAA" +
+ "AAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAAxgABAGQAZQABACcAAAAiAAMAAgAAAAq7AApZEgu3" +
+ "AAy/AAAAAQAoAAAABgABAAAAyQAJAGYAZwABACcAAAAiAAMABAAAAAq7AApZEgu3AAy/AAAAAQAo" +
+ "AAAABgABAAAAzQAJAGgAZwABACcAAAAiAAMABAAAAAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAA" +
+ "0QAJAGkAagABACcAAAAiAAMABAAAAAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAA1QAJAGsAagAB" +
+ "ACcAAAAiAAMABAAAAAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAA2QAJAGwAagABACcAAAAcAAQA" +
+ "BAAAAAQeIGGtAAAAAQAoAAAABgABAAAA4AAJAG0AagABACcAAAAiAAMABAAAAAq7AApZEgu3AAy/" +
+ "AAAAAQAoAAAABgABAAAA5AAJAG4AagABACcAAAAiAAMABAAAAAq7AApZEgu3AAy/AAAAAQAoAAAA" +
+ "BgABAAAA5xBBAGQAbwABACcAAAAhAAIAAgAAAAkqK8AADbYAEKwAAAABACgAAAAGAAEAAAAFAAgA" +
+ "cABxAAEAJwAAACEAAQAAAAAABQGzABGxAAAAAQAoAAAACgACAAAACAAEAAoAAgAcAAAAAgByAHMA" +
+ "AAACAHQ=");
+ private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzNQAFtMupmeDN6Ck5nxdemGsp43KmLNpYLrMYFgAAcAAAAHhWNBIAAAAAAAAAAEgVAABl" +
+ "AAAAcAAAABUAAAAEAgAAIAAAAFgCAAAHAAAA2AMAAD0AAAAQBAAAAQAAAPgFAAAAEAAAGAYAAB4O" +
+ "AAAhDgAAKw4AADMOAAA3DgAAOg4AAEEOAABEDgAARw4AAEoOAABODgAAVg4AAFsOAABfDgAAYg4A" +
+ "AGYOAABrDgAAcA4AAHQOAAB5DgAAfA4AAIAOAACEDgAAiQ4AAI0OAACSDgAAlw4AAJwOAAC7DgAA" +
+ "1w4AAOkOAAD8DgAAEw8AACsPAAA+DwAAUA8AAGQPAACHDwAAmw8AAK8PAADKDwAA4g8AAO0PAAD4" +
+ "DwAAAxAAABsQAAA/EAAAQhAAAEgQAABOEAAAURAAAFUQAABbEAAAXxAAAGIQAABmEAAAahAAAHIQ" +
+ "AAB8EAAAhxAAAJAQAACbEAAArBAAALQQAADEEAAA0RAAAOUQAADtEAAA+RAAAA0RAAAXEQAAIBEA" +
+ "ACoRAAA5EQAAQxEAAE4RAABcEQAAYREAAGYRAAB8EQAAkxEAAJ4RAACxEQAAxBEAAM0RAADbEQAA" +
+ "5xEAAPQRAAAGEgAAEhIAABoSAAAmEgAAKxIAADsSAABIEgAAVxIAAGESAAB3EgAAiRIAAJwSAACj" +
+ "EgAABAAAAAYAAAAHAAAACAAAAA0AAAAbAAAAHAAAAB4AAAAgAAAAIQAAACIAAAAjAAAAJAAAACUA" +
+ "AAAmAAAAJwAAACgAAAAuAAAAMQAAADUAAAA3AAAABAAAAAAAAAAAAAAABgAAAAEAAAAAAAAABwAA" +
+ "AAIAAAAAAAAACAAAAAMAAAAAAAAACQAAAAMAAAC0DQAACgAAAAMAAAC8DQAACwAAAAMAAADMDQAA" +
+ "DAAAAAMAAADUDQAADAAAAAMAAADcDQAADQAAAAQAAAAAAAAADgAAAAQAAAC0DQAADwAAAAQAAADk" +
+ "DQAAEAAAAAQAAADMDQAAEQAAAAQAAADsDQAAEgAAAAQAAAD0DQAAFQAAAAoAAAC0DQAAFwAAAAoA" +
+ "AADsDQAAGAAAAAoAAAD0DQAAGQAAAAoAAAD8DQAAGgAAAAoAAAAEDgAAEwAAAA4AAAAAAAAAFQAA" +
+ "AA4AAAC0DQAAFgAAAA4AAADkDQAAFAAAAA8AAAAMDgAAFwAAAA8AAADsDQAAFQAAABAAAAC0DQAA" +
+ "LgAAABEAAAAAAAAAMQAAABIAAAAAAAAAMgAAABIAAAC0DQAAMwAAABIAAAAUDgAANAAAABIAAADs" +
+ "DQAANgAAABMAAADcDQAACgADAAUAAAAKAAQAKgAAAAoABAArAAAACgADAC8AAAAKAAcAMAAAAAoA" +
+ "BABXAAAACgAEAGMAAAAJAB4AAgAAAAoAGwABAAAACgAcAAIAAAAKAB4AAgAAAAoABAA5AAAACgAA" +
+ "ADoAAAAKAAYAOwAAAAoABwA8AAAACgAIADwAAAAKAAYAPQAAAAoAEAA+AAAACgAMAD8AAAAKAAEA" +
+ "QAAAAAoAHwBCAAAACgACAEMAAAAKAAUARAAAAAoAHQBFAAAACgAQAEYAAAAKABIARgAAAAoAEwBG" +
+ "AAAACgADAEcAAAAKAAQARwAAAAoACgBIAAAACgADAEkAAAAKAAkASgAAAAoACgBLAAAACgAMAEwA" +
+ "AAAKAAwATQAAAAoABABOAAAACgAEAE8AAAAKAA0AUAAAAAoADgBQAAAACgANAFEAAAAKAA4AUQAA" +
+ "AAoADABSAAAACgAKAFMAAAAKAAoAVAAAAAoACwBVAAAACgALAFYAAAAKABoAWAAAAAoABABZAAAA" +
+ "CgAEAFoAAAAKAAwAWwAAAAoAFQBcAAAACgAVAF0AAAAKABUAXgAAAAoAFABfAAAACgAVAF8AAAAK" +
+ "ABYAXwAAAAoAGQBgAAAACgAVAGEAAAAKABYAYQAAAAoAFgBiAAAACgAPAGQAAAAKABAAZAAAAAoA" +
+ "EQBkAAAACwAbAAIAAAAPABsAAgAAAA8AFwA4AAAADwAYADgAAAAPABQAXwAAAAoAAAARAAAACwAA" +
+ "AKwNAAApAAAAVA0AAFEUAABIFAAAAQAAACIUAAABAAAAMhQAAAEAAABAFAAAAAAAAAAAAACsEgAA" +
+ "AQAAAA4AAAAEAAMAAQAAALESAAAGAAAAcBA4AAEAWhIGAA4ABAACAAMAAAC6EgAADgAAABYAAABw" +
+ "MAIAAgEiAAkAGwEsAAAAcCAAABAAJwADAAIAAAAAAMISAAACAAAAElAPAAYABAACAAAAyBIAAAkA" +
+ "AAAiAAkAGwEsAAAAcCAAABAAJwAAAAYABAACAAAA0BIAAAkAAAAiAAkAGwEsAAAAcCAAABAAJwAA" +
+ "AAMAAQACAAAA2BIAAAkAAAAiAAkAGwEsAAAAcCAAABAAJwAAAAYABAACAAAA3xIAAAkAAAAiAAkA" +
+ "GwEsAAAAcCAAABAAJwAAAAgABgACAAAA5xIAAAkAAAAiAAkAGwEsAAAAcCAAABAAJwAAAAYABAAC" +
+ "AAAA8RIAAAkAAAAiAAkAGwEsAAAAcCAAABAAJwAAAAMAAQACAAAA+RIAAAkAAAAiAAkAGwEsAAAA" +
+ "cCAAABAAJwAAAAUAAwACAAAAABMAAAkAAAAiAAkAGwEsAAAAcCAAABAAJwAAAAQAAgACAAAACBMA" +
+ "AAkAAAAiAAkAGwEsAAAAcCAAABAAJwAAAAQAAgACAAAAEBMAAAkAAAAiAAkAGwEsAAAAcCAAABAA" +
+ "JwAAAAQAAgAAAAAAFxMAAAQAAAAWAAEAuyAQAAQAAgAAAAAAHRMAAAUAAAAWAAEAnAACABAAAAAG" +
+ "AAQAAgAAACMTAAAJAAAAIgAJABsBLAAAAHAgAAAQACcAAAAGAAQAAgAAACsTAAAJAAAAIgAJABsB" +
+ "LAAAAHAgAAAQACcAAAAEAAIAAAAAADMTAAAEAAAAmwACAoQADwAEAAIAAAAAADkTAAAGAAAAEhCl" +
+ "AAIAwCCEAA8AAwABAAIAAAA/EwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABAACAAIAAABFEwAA" +
+ "CQAAACIACQAbASwAAABwIAAAEAAnAAAAAwABAAIAAABMEwAACQAAACIACQAbASwAAABwIAAAEAAn" +
+ "AAAABAACAAIAAABSEwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABgAEAAIAAABZEwAACQAAACIA" +
+ "CQAbASwAAABwIAAAEAAnAAAABAACAAAAAABhEwAAAgAAAH0gEAAEAAIAAAAAAGcTAAADAAAAFgAA" +
+ "ABAAAAADAAMAAAAAAG0TAAABAAAAEAAAAAUAAwAAAAAAdBMAAAQAAAAWAAoAvSAQAAMAAgAAAAAA" +
+ "exMAAAIAAAASAA8ABAACAAIAAACBEwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABgAEAAAAAACH" +
+ "EwAAAwAAAJsAAgQQAAAABAACAAIAAACPEwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABAACAAIA" +
+ "AACVEwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABAACAAIAAACbEwAACQAAACIACQAbASwAAABw" +
+ "IAAAEAAnAAAABAACAAIAAAChEwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABQADAAIAAACnEwAA" +
+ "CQAAACIACQAbASwAAABwIAAAEAAnAAAABAACAAIAAACuEwAACQAAACIACQAbASwAAABwIAAAEAAn" +
+ "AAAABAACAAIAAAC0EwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABQADAAIAAAC6EwAACQAAACIA" +
+ "CQAbASwAAABwIAAAEAAnAAAABQADAAIAAADBEwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABAAC" +
+ "AAMAAADIEwAABgAAACIACgBwMAIAIAMRAAMAAQACAAAAzxMAAAkAAAAiAAkAGwEsAAAAcCAAABAA" +
+ "JwAAAAQAAgACAAAA1hMAAAkAAAAiAAkAGwEsAAAAcCAAABAAJwAAAAMAAQACAAAA3hMAAAkAAAAi" +
+ "AAkAGwEsAAAAcCAAABAAJwAAAAQAAgACAAAA5BMAAAkAAAAiAAkAGwEsAAAAcCAAABAAJwAAAAMA" +
+ "AgACAAAA6xMAAAcAAAAfAgoAbiAHACEACgAPAAAAAwABAAIAAADyEwAACQAAACIACQAbASwAAABw" +
+ "IAAAEAAnAAAABAACAAIAAAD4EwAACQAAACIACQAbASwAAABwIAAAEAAnAAAAAwABAAIAAAD/EwAA" +
+ "CQAAACIACQAbASwAAABwIAAAEAAnAAAAAwABAAIAAAAFFAAACQAAACIACQAbASwAAABwIAAAEAAn" +
+ "AAAAAwABAAIAAAALFAAACQAAACIACQAbASwAAABwIAAAEAAnAAAAAwABAAAAAAARFAAAAwAAAFMg" +
+ "BgAQAAAAAwABAAIAAAAXFAAACQAAACIACQAbASwAAABwIAAAEAAnAAAABQABAAMAAAAdFAAAGAAA" +
+ "ACIADwBwEDkAAAAbAS0AAABuIDsAEAAMAFNCBgCGIm4wOgAgAwwAbhA8AAAADAARABgGAAABAAAA" +
+ "CAAAAAAAAAAEAAAAIAYAAAMAAAAoBgAACgAAACgGAAAeAAAAKAYAAB8AAAAoBgAAIAAAACgGAAAh" +
+ "AAAAKAYAADYAAAAoBgAANwAAACgGAAABAAAACAAAAAEAAAAEAAAABQAAAAQAAwAUAAMAAwAAAAIA" +
+ "AAAEAAQAAQAAAAoAAAABAAAADQAAAAIAAAAEAAMAAQAAAA4AAAACAAAADgADAAIAAAAOAAQAAgAA" +
+ "AA4ACgABAAAAAQAAAAMAAAAEAAMAFAABPAAIPGNsaW5pdD4ABjxpbml0PgACPjsAAUIABUJZVEVT" +
+ "AAFEAAFGAAFJAAJJSgAGSUpJTElJAANJSkoAAklMAAFKAAJKSgADSkpJAANKSkoAAkpMAANKTEkA" +
+ "AUwAAkxEAAJMSgADTEpJAAJMTAADTExJAANMTEoAA0xMTAAdTGRhbHZpay9hbm5vdGF0aW9uL1Np" +
+ "Z25hdHVyZTsAGkxkYWx2aWsvYW5ub3RhdGlvbi9UaHJvd3M7ABBMamF2YS9sYW5nL0NsYXNzABFM" +
+ "amF2YS9sYW5nL0NsYXNzOwAVTGphdmEvbGFuZy9Db21wYXJhYmxlABZMamF2YS9sYW5nL0NvbXBh" +
+ "cmFibGU7ABFMamF2YS9sYW5nL0Vycm9yOwAQTGphdmEvbGFuZy9Mb25nOwASTGphdmEvbGFuZy9O" +
+ "dW1iZXI7ACFMamF2YS9sYW5nL051bWJlckZvcm1hdEV4Y2VwdGlvbjsAEkxqYXZhL2xhbmcvT2Jq" +
+ "ZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7ABZMamF2" +
+ "YS9tYXRoL0JpZ0ludGVnZXI7AAlMb25nLmphdmEACU1BWF9WQUxVRQAJTUlOX1ZBTFVFABZNZXRo" +
+ "b2QgcmVkZWZpbmVkIGF3YXkhACJSZWRlZmluZWQgTG9uZyEgdmFsdWUgKGFzIGRvdWJsZSk9AAFT" +
+ "AARTSVpFAARUWVBFAAFWAAJWSgAEVkpJTAACVkwAAVoAAlpMAAJbQwAGYXBwZW5kAAhiaXRDb3Vu" +
+ "dAAJYnl0ZVZhbHVlAAdjb21wYXJlAAljb21wYXJlVG8AD2NvbXBhcmVVbnNpZ25lZAAGZGVjb2Rl" +
+ "AA5kaXZpZGVVbnNpZ25lZAALZG91YmxlVmFsdWUAEmVtaXR0ZXI6IGphY2stNC4yNQAGZXF1YWxz" +
+ "AApmbG9hdFZhbHVlABJmb3JtYXRVbnNpZ25lZExvbmcACGdldENoYXJzAAdnZXRMb25nAAhoYXNo" +
+ "Q29kZQANaGlnaGVzdE9uZUJpdAAIaW50VmFsdWUACWxvbmdWYWx1ZQAMbG93ZXN0T25lQml0AANt" +
+ "YXgAA21pbgAUbnVtYmVyT2ZMZWFkaW5nWmVyb3MAFW51bWJlck9mVHJhaWxpbmdaZXJvcwAJcGFy" +
+ "c2VMb25nABFwYXJzZVVuc2lnbmVkTG9uZwARcmVtYWluZGVyVW5zaWduZWQAB3JldmVyc2UADHJl" +
+ "dmVyc2VCeXRlcwAKcm90YXRlTGVmdAALcm90YXRlUmlnaHQAEHNlcmlhbFZlcnNpb25VSUQACnNo" +
+ "b3J0VmFsdWUABnNpZ251bQAKc3RyaW5nU2l6ZQADc3VtAA50b0JpbmFyeVN0cmluZwALdG9IZXhT" +
+ "dHJpbmcADXRvT2N0YWxTdHJpbmcACHRvU3RyaW5nABR0b1Vuc2lnbmVkQmlnSW50ZWdlcgAQdG9V" +
+ "bnNpZ25lZFN0cmluZwARdG9VbnNpZ25lZFN0cmluZzAABXZhbHVlAAd2YWx1ZU9mAAUABw4AkAEB" +
+ "AAcOPC0AlQEBAAcOWgAiAQAHDgDNAQIAAAcOANEBAgAABw4AiwEBAAcOANUBAgAABw4AXQUAAAAA" +
+ "AAcOAGkDAAAABw4AvgEBAAcOAMIBAgAABw4AxgECAAAHDgC2AQEABw4ADgEABw4AEwEABw4A5AEC" +
+ "AAAHDgDnAQIAAAcOABgBAAcOAB0BAAcOAHUBAAcOAHECAAAHDgB9AQAHDgB5AgAABw4A2QECAAAH" +
+ "DgAxAQAHDgA7AQAHDgAnAgAABw4ALAIAAAcOADYBAAcOAG0BAAcOAOABAgAABw4AVQEABw4ATQEA" +
+ "Bw4AUQEABw4AYQEABw4AQwIAAAcOAEoBAAcOAGUBAAcOAEYCAAAHDgBZAgAABw4AhwEBAAcOAIQB" +
+ "AQAHDgCBAQIAAAcOAJoBAAcOAMkBAQAHDgDIAQEABw4ArgEABw4AugEBAAcOAKoBAAcOALIBAAcO" +
+ "AKIBAAcOAKYBAAcOAJ4BAAcOAD8ABw4AAgUBYxwFFyMXHxcAFyIXAwIFAWMcBBcdFwAXIhcDAgYB" +
+ "YxwBGAwEBAgGAAYABEAGASwLABkBGQEZARkBGQEaBhIBiIAEsAwBgYAExAwBgYAE4AwBCYwNAgmg" +
+ "DQMJxA0BCegNAQmMDgQIsA4BCNQOAQn4DgEJnA8BCcAPAgnkDwEJiBADCaAQAQm8EAEJ4BABCYQR" +
+ "AQmcEQEJuBEBCdwRAQmAEgEJpBIBCcgSAQnsEgEJgBMBCZgTAQmsEwIJxBMBCNgTAQn8EwEJlBQB" +
+ "CbgUAQncFAIJgBUBCaQVAQrIFQEJ7BUBCZAWAQi0FgEJ2BYBCfQWAQmYFwUBvBcCAeAXAcEghBgE" +
+ "AaQYAQHIGAEB7BgGAZAZAwG0GQEB2BkPAfAZBwGUGgAAEQAAAAAAAAABAAAAAAAAAAEAAABlAAAA" +
+ "cAAAAAIAAAAVAAAABAIAAAMAAAAgAAAAWAIAAAQAAAAHAAAA2AMAAAUAAAA9AAAAEAQAAAYAAAAB" +
+ "AAAA+AUAAAMQAAADAAAAGAYAAAEgAAA3AAAAMAYAAAYgAAABAAAAVA0AAAEQAAANAAAArA0AAAIg" +
+ "AABlAAAAHg4AAAMgAAA3AAAArBIAAAQgAAADAAAAIhQAAAUgAAABAAAASBQAAAAgAAABAAAAURQA" +
+ "AAAQAAABAAAASBUAAA==");
+
+ static class FuncCmp implements LongPredicate {
+ final String name;
+ final LongPredicate p;
+ public FuncCmp(String name, LongPredicate p) {
+ this.name = name;
+ this.p = p;
+ }
+
+ public boolean test(long l) {
+ return p.test(l);
+ }
+ }
+ static FuncCmp l2l(String name, final LongUnaryOperator a, final LongUnaryOperator b) {
+ return new FuncCmp(name, (v) -> a.applyAsLong(v) == b.applyAsLong(v));
+ }
+ static FuncCmp l2i(String name, final LongToIntFunction a, final LongToIntFunction b) {
+ return new FuncCmp(name, (v) -> a.applyAsInt(v) == b.applyAsInt(v));
+ }
+
+ /** Interface for a long, int -> long function. */
+ static interface LI2IFunction { public long applyToLongInt(long a, int b); }
+
+ static FuncCmp li2l(String name, final Random r, final LI2IFunction a, final LI2IFunction b) {
+ return new FuncCmp(name, new LongPredicate() {
+ public boolean test(long v) {
+ int i = r.nextInt();
+ return a.applyToLongInt(v, i) == b.applyToLongInt(v, i);
+ }
+ });
+ }
+
+ public static void main(String[] args) {
+ doTest(10000);
+ }
+
+ public static void doTest(int iters) {
+ // Just transform immediately.
+ doCommonClassRedefinition(Long.class, CLASS_BYTES, DEX_BYTES);
+ final Random r = new Random();
+ FuncCmp[] comps = new FuncCmp[] {
+ l2l("highestOneBit", Long::highestOneBit, RedefinedLongIntrinsics::highestOneBit),
+ l2l("lowestOneBit", Long::lowestOneBit, RedefinedLongIntrinsics::lowestOneBit),
+ l2i("numberOfLeadingZeros",
+ Long::numberOfLeadingZeros,
+ RedefinedLongIntrinsics::numberOfLeadingZeros),
+ l2i("numberOfTrailingZeros",
+ Long::numberOfTrailingZeros,
+ RedefinedLongIntrinsics::numberOfTrailingZeros),
+ l2i("bitCount", Long::bitCount, RedefinedLongIntrinsics::bitCount),
+ li2l("rotateLeft", r, Long::rotateLeft, RedefinedLongIntrinsics::rotateLeft),
+ li2l("rotateRight", r, Long::rotateRight, RedefinedLongIntrinsics::rotateRight),
+ l2l("reverse", Long::reverse, RedefinedLongIntrinsics::reverse),
+ l2i("signum", Long::signum, RedefinedLongIntrinsics::signum),
+ l2l("reverseBytes", Long::reverseBytes, RedefinedLongIntrinsics::reverseBytes),
+ };
+ for (FuncCmp f : comps) {
+ // Just actually use ints so we can cast them back int the tests to print them (since we
+ // deleted a bunch of the Long methods needed for printing longs)!
+ int failures = (int)r.ints(iters)
+ .mapToLong((v) -> (long)v)
+ .filter(f.negate()) // Get all the test cases that failed.
+ .count();
+ if (failures != 0) {
+ double percent = 100.0d*((double)failures/(double)iters);
+ System.out.println("for intrinsic " + f.name + " " + failures + "/" + iters
+ + " (" + Double.toString(percent) + "%) tests failed!");
+ }
+ }
+ System.out.println("Finished!");
+ }
+
+ // Transforms the class
+ private static native void doCommonClassRedefinition(Class<?> target,
+ byte[] class_file,
+ byte[] dex_file);
+}
diff --git a/test/950-redefine-intrinsic/src/RedefinedLongIntrinsics.java b/test/950-redefine-intrinsic/src/RedefinedLongIntrinsics.java
new file mode 100644
index 0000000..0ada4a6
--- /dev/null
+++ b/test/950-redefine-intrinsic/src/RedefinedLongIntrinsics.java
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+/**
+ * The methods that are intrinsified in Long and their expected redefined implementations.
+ */
+class RedefinedLongIntrinsics {
+ // Intrinsic! Do something cool. Return i + 1
+ public static long highestOneBit(long i) {
+ return i + 1;
+ }
+
+ // Intrinsic! Do something cool. Return i - 1
+ public static long lowestOneBit(long i) {
+ return i - 1;
+ }
+
+ // Intrinsic! Do something cool. Return i + i
+ public static int numberOfLeadingZeros(long i) {
+ return (int)(i + i);
+ }
+
+ // Intrinsic! Do something cool. Return i & (i >>> 1);
+ public static int numberOfTrailingZeros(long i) {
+ return (int)(i & (i >>> 1));
+ }
+
+ // Intrinsic! Do something cool. Return 5
+ public static int bitCount(long i) {
+ return 5;
+ }
+
+ // Intrinsic! Do something cool. Return i
+ public static long rotateLeft(long i, int distance) {
+ return i;
+ }
+
+ // Intrinsic! Do something cool. Return 10 * i
+ public static long rotateRight(long i, int distance) {
+ return 10 * i;
+ }
+
+ // Intrinsic! Do something cool. Return -i
+ public static long reverse(long i) {
+ return -i;
+ }
+
+ // Intrinsic! Do something cool. Return 0
+ public static int signum(long i) {
+ return 0;
+ }
+
+ // Intrinsic! Do something cool. Return 0
+ public static long reverseBytes(long i) {
+ return 0;
+ }
+}
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index c14a0b2..95967b5 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -170,12 +170,6 @@
ifeq ($(ART_TEST_RUN_TEST_MULTI_IMAGE),true)
IMAGE_TYPES := multipicimage
endif
-ifeq ($(ART_TEST_NPIC_IMAGE),true)
- IMAGE_TYPES += npicimage
- ifeq ($(ART_TEST_RUN_TEST_MULTI_IMAGE),true)
- IMAGE_TYPES := multinpicimage
- endif
-endif
PICTEST_TYPES := npictest
ifeq ($(ART_TEST_PIC_TEST),true)
PICTEST_TYPES += pictest
@@ -878,21 +872,13 @@
endif
endif
ifeq ($(2),no-image)
- $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_pic_$(4))
+ $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_$(4))
else
- ifeq ($(2),npicimage)
- $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_no-pic_$(4))
+ ifeq ($(2),picimage)
+ $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_$(4))
else
- ifeq ($(2),picimage)
- $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_pic_$(4))
- else
- ifeq ($(2),multinpicimage)
- $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_no-pic_multi_$(4))
- else
- ifeq ($(2),multipicimage)
- $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_pic_multi_$(4))
- endif
- endif
+ ifeq ($(2),multipicimage)
+ $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_multi_$(4))
endif
endif
endif
@@ -1093,50 +1079,29 @@
# Add the core dependency. This is required for pre-building.
# Use the PIC image, as it is the default in run-test, to match dependencies.
ifeq ($(1),host)
- prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_pic_$(13))
+ prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_$(13))
else
- prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_pic_$(13))
+ prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_$(13))
endif
else
- ifeq ($(9),npicimage)
- test_groups += ART_RUN_TEST_$$(uc_host_or_target)_IMAGE_RULES
- run_test_options += --npic-image
- # Add the core dependency.
+ ifeq ($(9),picimage)
+ test_groups += ART_RUN_TEST_$$(uc_host_or_target)_PICIMAGE_RULES
ifeq ($(1),host)
- prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_no-pic_$(13))
+ prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_$(13))
else
- prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_no-pic_$(13))
+ prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_$(13))
endif
else
- ifeq ($(9),picimage)
+ ifeq ($(9),multipicimage)
test_groups += ART_RUN_TEST_$$(uc_host_or_target)_PICIMAGE_RULES
+ run_test_options += --multi-image
ifeq ($(1),host)
- prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_pic_$(13))
+ prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_multi_$(13))
else
- prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_pic_$(13))
+ prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_multi_$(13))
endif
else
- ifeq ($(9),multinpicimage)
- test_groups += ART_RUN_TEST_$$(uc_host_or_target)_IMAGE_RULES
- run_test_options += --npic-image --multi-image
- ifeq ($(1),host)
- prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_no-pic_multi_$(13))
- else
- prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_no-pic_multi_$(13))
- endif
- else
- ifeq ($(9),multipicimage)
- test_groups += ART_RUN_TEST_$$(uc_host_or_target)_PICIMAGE_RULES
- run_test_options += --multi-image
- ifeq ($(1),host)
- prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_pic_multi_$(13))
- else
- prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_pic_multi_$(13))
- endif
- else
- $$(error found $(9) expected $(IMAGE_TYPES))
- endif
- endif
+ $$(error found $(9) expected $(IMAGE_TYPES))
endif
endif
endif
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 7d218f1..f3d4332 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -19,7 +19,7 @@
DEV_MODE="n"
DEX2OAT=""
EXPERIMENTAL=""
-FALSE_BIN="/system/bin/false"
+FALSE_BIN="false"
FLAGS=""
ANDROID_FLAGS=""
GDB=""
diff --git a/test/run-test b/test/run-test
index d55ba77..e808dee 100755
--- a/test/run-test
+++ b/test/run-test
@@ -80,7 +80,7 @@
# ANDROID_HOST_OUT is not set in a build environment.
if [ -z "$ANDROID_HOST_OUT" ]; then
- export ANDROID_HOST_OUT=$ANDROID_BUILD_TOP/out/host/linux-x86
+ export ANDROID_HOST_OUT=${OUT_DIR:-$ANDROID_BUILD_TOP/out/}host/linux-x86
fi
# If JACK_CLASSPATH is not set, assume it only contains core-libart.
@@ -143,7 +143,6 @@
have_dex2oat="yes"
have_patchoat="yes"
have_image="yes"
-pic_image_suffix=""
multi_image_suffix=""
android_root="/system"
bisection_search="no"
@@ -199,9 +198,6 @@
elif [ "x$1" = "x--no-image" ]; then
have_image="no"
shift
- elif [ "x$1" = "x--npic-image" ]; then
- pic_image_suffix="-npic"
- shift
elif [ "x$1" = "x--multi-image" ]; then
multi_image_suffix="-multi"
shift
@@ -507,12 +503,12 @@
elif [ "$runtime" = "art" ]; then
if [ "$target_mode" = "no" ]; then
guess_host_arch_name
- run_args="${run_args} --boot ${ANDROID_HOST_OUT}/framework/core${image_suffix}${pic_image_suffix}${multi_image_suffix}.art"
+ run_args="${run_args} --boot ${ANDROID_HOST_OUT}/framework/core${image_suffix}${multi_image_suffix}.art"
run_args="${run_args} --runtime-option -Djava.library.path=${ANDROID_HOST_OUT}/lib${suffix64}:${ANDROID_HOST_OUT}/nativetest${suffix64}"
else
guess_target_arch_name
run_args="${run_args} --runtime-option -Djava.library.path=/data/nativetest${suffix64}/art/${target_arch_name}"
- run_args="${run_args} --boot /data/art-test/core${image_suffix}${pic_image_suffix}${multi_image_suffix}.art"
+ run_args="${run_args} --boot /data/art-test/core${image_suffix}${multi_image_suffix}.art"
fi
if [ "$relocate" = "yes" ]; then
run_args="${run_args} --relocate"
@@ -668,8 +664,6 @@
echo " --dex2oat-swap Use a dex2oat swap file."
echo " --instruction-set-features [string]"
echo " Set instruction-set-features for compilation."
- echo " --npic-image Use an image compiled with non-position independent code "
- echo " for the boot class path."
echo " --multi-image Use a set of images compiled with dex2oat multi-image for"
echo " the boot class path."
echo " --pic-test Compile the test code position independent."
diff --git a/test/testrunner/env.py b/test/testrunner/env.py
index 0b69718..4336d77 100644
--- a/test/testrunner/env.py
+++ b/test/testrunner/env.py
@@ -105,9 +105,6 @@
# Do you want to test the optimizing compiler with graph coloring register allocation?
ART_TEST_OPTIMIZING_GRAPH_COLOR = getEnvBoolean('ART_TEST_OPTIMIZING_GRAPH_COLOR', ART_TEST_FULL)
-# Do we want to test a non-PIC-compiled core image?
-ART_TEST_NPIC_IMAGE = getEnvBoolean('ART_TEST_NPIC_IMAGE', ART_TEST_FULL)
-
# Do we want to test PIC-compiled tests ("apps")?
ART_TEST_PIC_TEST = getEnvBoolean('ART_TEST_PIC_TEST', ART_TEST_FULL)
# Do you want tracing tests run?
@@ -179,6 +176,8 @@
ART_TEST_WITH_STRACE = getEnvBoolean('ART_TEST_DEBUG_GC', False)
+EXTRA_DISABLED_TESTS = set(env.get("ART_TEST_RUN_TEST_SKIP", "").split())
+
TARGET_2ND_ARCH = get_build_var('TARGET_2ND_ARCH')
TARGET_ARCH = get_build_var('TARGET_ARCH')
if TARGET_2ND_ARCH:
diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py
index 5a6114e..8c0b928 100755
--- a/test/testrunner/testrunner.py
+++ b/test/testrunner/testrunner.py
@@ -44,10 +44,10 @@
In the end, the script will print the failed and skipped tests if any.
"""
+import argparse
import fnmatch
import itertools
import json
-from optparse import OptionParser
import os
import re
import subprocess
@@ -133,8 +133,7 @@
VARIANT_TYPE_DICT['run'] = {'ndebug', 'debug'}
VARIANT_TYPE_DICT['target'] = {'target', 'host'}
VARIANT_TYPE_DICT['trace'] = {'trace', 'ntrace', 'stream'}
- VARIANT_TYPE_DICT['image'] = {'picimage', 'no-image', 'npicimage',
- 'multinpicimage', 'multipicimage'}
+ VARIANT_TYPE_DICT['image'] = {'picimage', 'no-image', 'multipicimage'}
VARIANT_TYPE_DICT['debuggable'] = {'ndebuggable', 'debuggable'}
VARIANT_TYPE_DICT['gc'] = {'gcstress', 'gcverify', 'cms'}
VARIANT_TYPE_DICT['prebuild'] = {'no-prebuild', 'no-dex2oat', 'prebuild'}
@@ -218,10 +217,6 @@
IMAGE_TYPES.add('no-image')
if env.ART_TEST_RUN_TEST_MULTI_IMAGE:
IMAGE_TYPES.add('multipicimage')
- if env.ART_TEST_NPIC_IMAGE:
- IMAGE_TYPES.add('npicimage')
- if env.ART_TEST_RUN_TEST_MULTI_IMAGE:
- IMAGE_TYPES.add('multinpicimage')
if env.ART_TEST_RUN_TEST_IMAGE or not IMAGE_TYPES: # Default
IMAGE_TYPES.add('picimage')
@@ -388,10 +383,6 @@
if image == 'no-image':
options_test += ' --no-image'
- elif image == 'npicimage':
- options_test += ' --npic-image'
- elif image == 'multinpicimage':
- options_test += ' --npic-image --multi-image'
elif image == 'multipicimage':
options_test += ' --multi-image'
@@ -605,6 +596,8 @@
"""
if dry_run:
return True
+ if test in env.EXTRA_DISABLED_TESTS:
+ return True
variants_list = DISABLED_TEST_CONTAINER.get(test, {})
for variants in variants_list:
variants_present = True
@@ -720,23 +713,26 @@
global gdb
global gdb_arg
- parser = OptionParser()
- parser.add_option('-t', '--test', dest='test', help='name of the test')
- parser.add_option('-j', type='int', dest='n_thread')
+ parser = argparse.ArgumentParser(description="Runs all or a subset of the ART test suite.")
+ parser.add_argument('-t', '--test', dest='test', help='name of the test')
+ parser.add_argument('-j', type=int, dest='n_thread')
for variant in TOTAL_VARIANTS_SET:
flag = '--' + variant
flag_dest = variant.replace('-', '_')
if variant == '32' or variant == '64':
flag_dest = 'n' + flag_dest
- parser.add_option(flag, action='store_true', dest=flag_dest)
- parser.add_option('--verbose', '-v', action='store_true', dest='verbose')
- parser.add_option('--dry-run', action='store_true', dest='dry_run')
- parser.add_option('-b', '--build-dependencies', action='store_true', dest='build')
- parser.add_option('--gdb', action='store_true', dest='gdb')
- parser.add_option('--gdb-arg', dest='gdb_arg')
+ parser.add_argument(flag, action='store_true', dest=flag_dest)
+ parser.add_argument('--verbose', '-v', action='store_true', dest='verbose')
+ parser.add_argument('--dry-run', action='store_true', dest='dry_run')
+ parser.add_argument("--skip", action="append", dest="skips", default=[],
+ help="Skip the given test in all circumstances.")
+ parser.add_argument('-b', '--build-dependencies', action='store_true', dest='build')
+ parser.add_argument('--gdb', action='store_true', dest='gdb')
+ parser.add_argument('--gdb-arg', dest='gdb_arg')
- options = parser.parse_args()[0]
+ options = parser.parse_args()
test = ''
+ env.EXTRA_DISABLED_TESTS.update(set(options.skips))
if options.test:
test = parse_test_name(options.test)
if options.pictest:
@@ -799,10 +795,6 @@
TRACE_TYPES.add('ntrace')
if options.cms:
GC_TYPES.add('cms')
- if options.npicimage:
- IMAGE_TYPES.add('npicimage')
- if options.multinpicimage:
- IMAGE_TYPES.add('multinpicimage')
if options.multipicimage:
IMAGE_TYPES.add('multipicimage')
if options.verbose:
@@ -823,10 +815,8 @@
return test
def main():
- global verbose
gather_test_info()
user_requested_test = parse_option()
- verbose = True
setup_test_env()
if build:
build_targets = ''
diff --git a/tools/ahat/Android.mk b/tools/ahat/Android.mk
index 493eafb..f79377d 100644
--- a/tools/ahat/Android.mk
+++ b/tools/ahat/Android.mk
@@ -23,7 +23,6 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAR_MANIFEST := src/manifest.txt
LOCAL_JAVA_RESOURCE_FILES := \
- $(LOCAL_PATH)/src/help.html \
$(LOCAL_PATH)/src/style.css
LOCAL_STATIC_JAVA_LIBRARIES := perflib-prebuilt guavalib trove-prebuilt
@@ -79,8 +78,9 @@
# BUILD_HOST_DALVIK_JAVA_LIBRARY above.
AHAT_TEST_DUMP_JAR := $(LOCAL_BUILT_MODULE)
AHAT_TEST_DUMP_HPROF := $(intermediates.COMMON)/test-dump.hprof
+AHAT_TEST_DUMP_BASE_HPROF := $(intermediates.COMMON)/test-dump-base.hprof
-# Run ahat-test-dump.jar to generate test-dump.hprof
+# Run ahat-test-dump.jar to generate test-dump.hprof and test-dump-base.hprof
AHAT_TEST_DUMP_DEPENDENCIES := \
$(ART_HOST_EXECUTABLES) \
$(ART_HOST_SHARED_LIBRARY_DEPENDENCIES) \
@@ -93,12 +93,19 @@
$(AHAT_TEST_DUMP_HPROF): $(AHAT_TEST_DUMP_JAR) $(AHAT_TEST_DUMP_DEPENDENCIES)
$(PRIVATE_AHAT_TEST_ART) -cp $(PRIVATE_AHAT_TEST_DUMP_JAR) Main $@
+$(AHAT_TEST_DUMP_BASE_HPROF): PRIVATE_AHAT_TEST_ART := $(HOST_OUT_EXECUTABLES)/art
+$(AHAT_TEST_DUMP_BASE_HPROF): PRIVATE_AHAT_TEST_DUMP_JAR := $(AHAT_TEST_DUMP_JAR)
+$(AHAT_TEST_DUMP_BASE_HPROF): PRIVATE_AHAT_TEST_DUMP_DEPENDENCIES := $(AHAT_TEST_DUMP_DEPENDENCIES)
+$(AHAT_TEST_DUMP_BASE_HPROF): $(AHAT_TEST_DUMP_JAR) $(AHAT_TEST_DUMP_DEPENDENCIES)
+ $(PRIVATE_AHAT_TEST_ART) -cp $(PRIVATE_AHAT_TEST_DUMP_JAR) Main $@ --base
+
.PHONY: ahat-test
ahat-test: PRIVATE_AHAT_TEST_DUMP_HPROF := $(AHAT_TEST_DUMP_HPROF)
+ahat-test: PRIVATE_AHAT_TEST_DUMP_BASE_HPROF := $(AHAT_TEST_DUMP_BASE_HPROF)
ahat-test: PRIVATE_AHAT_TEST_JAR := $(AHAT_TEST_JAR)
ahat-test: PRIVATE_AHAT_PROGUARD_MAP := $(AHAT_TEST_DUMP_PROGUARD_MAP)
-ahat-test: $(AHAT_TEST_JAR) $(AHAT_TEST_DUMP_HPROF)
- java -Dahat.test.dump.hprof=$(PRIVATE_AHAT_TEST_DUMP_HPROF) -Dahat.test.dump.map=$(PRIVATE_AHAT_PROGUARD_MAP) -jar $(PRIVATE_AHAT_TEST_JAR)
+ahat-test: $(AHAT_TEST_JAR) $(AHAT_TEST_DUMP_HPROF) $(AHAT_TEST_DUMP_BASE_HPROF)
+ java -enableassertions -Dahat.test.dump.hprof=$(PRIVATE_AHAT_TEST_DUMP_HPROF) -Dahat.test.dump.base.hprof=$(PRIVATE_AHAT_TEST_DUMP_BASE_HPROF) -Dahat.test.dump.map=$(PRIVATE_AHAT_PROGUARD_MAP) -jar $(PRIVATE_AHAT_TEST_JAR)
# Clean up local variables.
AHAT_TEST_DUMP_DEPENDENCIES :=
diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt
index 8dfb4ab..133426f 100644
--- a/tools/ahat/README.txt
+++ b/tools/ahat/README.txt
@@ -1,22 +1,21 @@
AHAT - Android Heap Analysis Tool
Usage:
- java -jar ahat.jar [-p port] [--proguard-map FILE] FILE
- Launch an http server for viewing the given Android heap-dump FILE.
+ java -jar ahat.jar [OPTIONS] FILE
+ Launch an http server for viewing the given Android heap dump FILE.
- Options:
+ OPTIONS:
-p <port>
Serve pages on the given port. Defaults to 7100.
--proguard-map FILE
Use the proguard map FILE to deobfuscate the heap dump.
+ --baseline FILE
+ Diff the heap dump against the given baseline heap dump FILE.
+ --baseline-proguard-map FILE
+ Use the proguard map FILE to deobfuscate the baseline heap dump.
TODO:
- * Have a way to diff two heap dumps.
-
- * Add more tips to the help page.
- - Recommend how to start looking at a heap dump.
- - Say how to enable allocation sites.
- - Where to submit feedback, questions, and bug reports.
+ * Add a user guide.
* Dim 'image' and 'zygote' heap sizes slightly? Why do we even show these?
* Let user re-sort sites objects info by clicking column headers.
* Let user re-sort "Objects" list.
@@ -49,9 +48,9 @@
time.
* That we don't show the 'extra' column in the DominatedList if we are
showing all the instances.
- * That InstanceUtils.asString properly takes into account "offset" and
+ * That Instance.asString properly takes into account "offset" and
"count" fields, if they are present.
- * InstanceUtils.getDexCacheLocation
+ * Instance.getDexCacheLocation
Reported Issues:
* Request to be able to sort tables by size.
@@ -76,7 +75,14 @@
* Instance.isRoot and Instance.getRootTypes.
Release History:
- 0.9 Pending
+ 1.1 Feb 21, 2017
+ Show java.lang.ref.Reference referents as "unreachable" instead of null.
+
+ 1.0 Dec 20, 2016
+ Add support for diffing two heap dumps.
+ Remove native allocations view.
+ Remove outdated help page.
+ Significant refactoring of ahat internals.
0.8 Oct 18, 2016
Show sample path from GC root with field names in place of dominator path.
diff --git a/tools/ahat/src/AhatSnapshot.java b/tools/ahat/src/AhatSnapshot.java
deleted file mode 100644
index ba8243f..0000000
--- a/tools/ahat/src/AhatSnapshot.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.ProguardMap;
-import com.android.tools.perflib.heap.RootObj;
-import com.android.tools.perflib.heap.RootType;
-import com.android.tools.perflib.heap.Snapshot;
-import com.android.tools.perflib.heap.StackFrame;
-import com.android.tools.perflib.heap.StackTrace;
-
-import com.google.common.collect.Lists;
-
-import gnu.trove.TObjectProcedure;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A wrapper over the perflib snapshot that provides the behavior we use in
- * ahat.
- */
-class AhatSnapshot {
- private final Snapshot mSnapshot;
- private final List<Heap> mHeaps;
-
- // Map from Instance to the list of Instances it immediately dominates.
- private final Map<Instance, List<Instance>> mDominated
- = new HashMap<Instance, List<Instance>>();
-
- // Collection of objects whose immediate dominator is the SENTINEL_ROOT.
- private final List<Instance> mRooted = new ArrayList<Instance>();
-
- // Map from roots to their types.
- // Instances are only included if they are roots, and the collection of root
- // types is guaranteed to be non-empty.
- private final Map<Instance, Collection<RootType>> mRoots
- = new HashMap<Instance, Collection<RootType>>();
-
- private final Site mRootSite = new Site("ROOT");
- private final Map<Heap, Long> mHeapSizes = new HashMap<Heap, Long>();
-
- private final List<InstanceUtils.NativeAllocation> mNativeAllocations
- = new ArrayList<InstanceUtils.NativeAllocation>();
-
- /**
- * Create an AhatSnapshot from an hprof file.
- */
- public static AhatSnapshot fromHprof(File hprof, ProguardMap map) throws IOException {
- Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(hprof), map);
- snapshot.computeDominators();
- return new AhatSnapshot(snapshot);
- }
-
- /**
- * Construct an AhatSnapshot for the given perflib snapshot.
- * Ther user is responsible for calling snapshot.computeDominators before
- * calling this AhatSnapshot constructor.
- */
- private AhatSnapshot(Snapshot snapshot) {
- mSnapshot = snapshot;
- mHeaps = new ArrayList<Heap>(mSnapshot.getHeaps());
-
- final ClassObj javaLangClass = mSnapshot.findClass("java.lang.Class");
- for (Heap heap : mHeaps) {
- // Use a single element array for the total to act as a reference to a
- // long.
- final long[] total = new long[]{0};
- TObjectProcedure<Instance> processInstance = new TObjectProcedure<Instance>() {
- @Override
- public boolean execute(Instance inst) {
- Instance dominator = inst.getImmediateDominator();
- if (dominator != null) {
- total[0] += inst.getSize();
-
- if (dominator == Snapshot.SENTINEL_ROOT) {
- mRooted.add(inst);
- }
-
- // Properly label the class of a class object.
- if (inst instanceof ClassObj && javaLangClass != null && inst.getClassObj() == null) {
- inst.setClassId(javaLangClass.getId());
- }
-
- // Update dominated instances.
- List<Instance> instances = mDominated.get(dominator);
- if (instances == null) {
- instances = new ArrayList<Instance>();
- mDominated.put(dominator, instances);
- }
- instances.add(inst);
-
- // Update sites.
- List<StackFrame> path = Collections.emptyList();
- StackTrace stack = getStack(inst);
- int stackId = getStackTraceSerialNumber(stack);
- if (stack != null) {
- StackFrame[] frames = getStackFrames(stack);
- if (frames != null && frames.length > 0) {
- path = Lists.reverse(Arrays.asList(frames));
- }
- }
- mRootSite.add(stackId, 0, path.iterator(), inst);
-
- // Update native allocations.
- InstanceUtils.NativeAllocation alloc = InstanceUtils.getNativeAllocation(inst);
- if (alloc != null) {
- mNativeAllocations.add(alloc);
- }
- }
- return true;
- }
- };
- for (Instance instance : heap.getClasses()) {
- processInstance.execute(instance);
- }
- heap.forEachInstance(processInstance);
- mHeapSizes.put(heap, total[0]);
- }
-
- // Record the roots and their types.
- for (RootObj root : snapshot.getGCRoots()) {
- Instance inst = root.getReferredInstance();
- Collection<RootType> types = mRoots.get(inst);
- if (types == null) {
- types = new HashSet<RootType>();
- mRoots.put(inst, types);
- }
- types.add(root.getRootType());
- }
- }
-
- // Note: This method is exposed for testing purposes.
- public ClassObj findClass(String name) {
- return mSnapshot.findClass(name);
- }
-
- public Instance findInstance(long id) {
- return mSnapshot.findInstance(id);
- }
-
- public int getHeapIndex(Heap heap) {
- return mSnapshot.getHeapIndex(heap);
- }
-
- public Heap getHeap(String name) {
- return mSnapshot.getHeap(name);
- }
-
- /**
- * Returns a collection of instances whose immediate dominator is the
- * SENTINEL_ROOT.
- */
- public List<Instance> getRooted() {
- return mRooted;
- }
-
- /**
- * Returns true if the given instance is a root.
- */
- public boolean isRoot(Instance inst) {
- return mRoots.containsKey(inst);
- }
-
- /**
- * Returns the list of root types for the given instance, or null if the
- * instance is not a root.
- */
- public Collection<RootType> getRootTypes(Instance inst) {
- return mRoots.get(inst);
- }
-
- public List<Heap> getHeaps() {
- return mHeaps;
- }
-
- public Site getRootSite() {
- return mRootSite;
- }
-
- /**
- * Look up the site at which the given object was allocated.
- */
- public Site getSiteForInstance(Instance inst) {
- Site site = mRootSite;
- StackTrace stack = getStack(inst);
- if (stack != null) {
- StackFrame[] frames = getStackFrames(stack);
- if (frames != null) {
- List<StackFrame> path = Lists.reverse(Arrays.asList(frames));
- site = mRootSite.getChild(path.iterator());
- }
- }
- return site;
- }
-
- /**
- * Return a list of those objects immediately dominated by the given
- * instance.
- */
- public List<Instance> getDominated(Instance inst) {
- return mDominated.get(inst);
- }
-
- /**
- * Return the total size of reachable objects allocated on the given heap.
- */
- public long getHeapSize(Heap heap) {
- return mHeapSizes.get(heap);
- }
-
- /**
- * Return the class name for the given class object.
- * classObj may be null, in which case "(class unknown)" is returned.
- */
- public static String getClassName(ClassObj classObj) {
- if (classObj == null) {
- return "(class unknown)";
- }
- return classObj.getClassName();
- }
-
- // Return the stack where the given instance was allocated.
- private static StackTrace getStack(Instance inst) {
- return inst.getStack();
- }
-
- // Return the list of stack frames for a stack trace.
- private static StackFrame[] getStackFrames(StackTrace stack) {
- return stack.getFrames();
- }
-
- // Return the serial number of the given stack trace.
- private static int getStackTraceSerialNumber(StackTrace stack) {
- return stack.getSerialNumber();
- }
-
- // Get the site associated with the given stack id and depth.
- // Returns the root site if no such site found.
- // depth of -1 means the full stack.
- public Site getSite(int stackId, int depth) {
- Site site = mRootSite;
- StackTrace stack = mSnapshot.getStackTrace(stackId);
- if (stack != null) {
- StackFrame[] frames = getStackFrames(stack);
- if (frames != null) {
- List<StackFrame> path = Lists.reverse(Arrays.asList(frames));
- if (depth >= 0) {
- path = path.subList(0, depth);
- }
- site = mRootSite.getChild(path.iterator());
- }
- }
- return site;
- }
-
- // Return a list of known native allocations in the snapshot.
- public List<InstanceUtils.NativeAllocation> getNativeAllocations() {
- return mNativeAllocations;
- }
-}
diff --git a/tools/ahat/src/BitmapHandler.java b/tools/ahat/src/BitmapHandler.java
index 0f567e3..836aef6 100644
--- a/tools/ahat/src/BitmapHandler.java
+++ b/tools/ahat/src/BitmapHandler.java
@@ -16,7 +16,8 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.awt.image.BufferedImage;
@@ -38,9 +39,9 @@
Query query = new Query(exchange.getRequestURI());
long id = query.getLong("id", 0);
BufferedImage bitmap = null;
- Instance inst = mSnapshot.findInstance(id);
+ AhatInstance inst = mSnapshot.findInstance(id);
if (inst != null) {
- bitmap = InstanceUtils.asBitmap(inst);
+ bitmap = inst.asBitmap();
}
if (bitmap != null) {
diff --git a/tools/ahat/src/Column.java b/tools/ahat/src/Column.java
index b7f2829..819e586 100644
--- a/tools/ahat/src/Column.java
+++ b/tools/ahat/src/Column.java
@@ -22,14 +22,24 @@
class Column {
public DocString heading;
public Align align;
+ public boolean visible;
public static enum Align {
LEFT, RIGHT
};
- public Column(DocString heading, Align align) {
+ public Column(DocString heading, Align align, boolean visible) {
this.heading = heading;
this.align = align;
+ this.visible = visible;
+ }
+
+ public Column(String heading, Align align, boolean visible) {
+ this(DocString.text(heading), align, visible);
+ }
+
+ public Column(DocString heading, Align align) {
+ this(heading, align, true);
}
/**
diff --git a/tools/ahat/src/DocString.java b/tools/ahat/src/DocString.java
index 19666de..c6303c8 100644
--- a/tools/ahat/src/DocString.java
+++ b/tools/ahat/src/DocString.java
@@ -53,7 +53,6 @@
public static DocString link(URI uri, DocString content) {
DocString doc = new DocString();
return doc.appendLink(uri, content);
-
}
/**
@@ -86,6 +85,78 @@
return this;
}
+ /**
+ * Adorn the given string to indicate it represents something added relative
+ * to a baseline.
+ */
+ public static DocString added(DocString str) {
+ DocString string = new DocString();
+ string.mStringBuilder.append("<span class=\"added\">");
+ string.mStringBuilder.append(str.html());
+ string.mStringBuilder.append("</span>");
+ return string;
+ }
+
+ /**
+ * Adorn the given string to indicate it represents something added relative
+ * to a baseline.
+ */
+ public static DocString added(String str) {
+ return added(text(str));
+ }
+
+ /**
+ * Adorn the given string to indicate it represents something removed relative
+ * to a baseline.
+ */
+ public static DocString removed(DocString str) {
+ DocString string = new DocString();
+ string.mStringBuilder.append("<span class=\"removed\">");
+ string.mStringBuilder.append(str.html());
+ string.mStringBuilder.append("</span>");
+ return string;
+ }
+
+ /**
+ * Adorn the given string to indicate it represents something removed relative
+ * to a baseline.
+ */
+ public static DocString removed(String str) {
+ return removed(text(str));
+ }
+
+ /**
+ * Standard formatted DocString for describing a change in size relative to
+ * a baseline.
+ * @param noCurrent - whether no current object exists.
+ * @param noBaseline - whether no basline object exists.
+ * @param current - the size of the current object.
+ * @param baseline - the size of the baseline object.
+ */
+ public static DocString delta(boolean noCurrent, boolean noBaseline,
+ long current, long baseline) {
+ DocString doc = new DocString();
+ return doc.appendDelta(noCurrent, noBaseline, current, baseline);
+ }
+
+ /**
+ * Standard formatted DocString for describing a change in size relative to
+ * a baseline.
+ */
+ public DocString appendDelta(boolean noCurrent, boolean noBaseline,
+ long current, long baseline) {
+ if (noCurrent) {
+ append(removed(format("%+,14d", 0 - baseline)));
+ } else if (noBaseline) {
+ append(added("new"));
+ } else if (current > baseline) {
+ append(added(format("%+,14d", current - baseline)));
+ } else if (current < baseline) {
+ append(removed(format("%+,14d", current - baseline)));
+ }
+ return this;
+ }
+
public DocString appendLink(URI uri, DocString content) {
mStringBuilder.append("<a href=\"");
mStringBuilder.append(uri.toASCIIString());
diff --git a/tools/ahat/src/DominatedList.java b/tools/ahat/src/DominatedList.java
index 7a673f5..f73e3ca 100644
--- a/tools/ahat/src/DominatedList.java
+++ b/tools/ahat/src/DominatedList.java
@@ -16,8 +16,10 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Sort;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -39,39 +41,32 @@
* @param instances the collection of instances to generate a list for
*/
public static void render(final AhatSnapshot snapshot,
- Doc doc, Query query, String id, Collection<Instance> instances) {
- List<Instance> insts = new ArrayList<Instance>(instances);
+ Doc doc, Query query, String id, Collection<AhatInstance> instances) {
+ List<AhatInstance> insts = new ArrayList<AhatInstance>(instances);
Collections.sort(insts, Sort.defaultInstanceCompare(snapshot));
- HeapTable.render(doc, query, id, new TableConfig(snapshot), snapshot, insts);
+ HeapTable.render(doc, query, id, new TableConfig(), snapshot, insts);
}
- private static class TableConfig implements HeapTable.TableConfig<Instance> {
- AhatSnapshot mSnapshot;
-
- public TableConfig(AhatSnapshot snapshot) {
- mSnapshot = snapshot;
- }
-
+ private static class TableConfig implements HeapTable.TableConfig<AhatInstance> {
@Override
public String getHeapsDescription() {
return "Bytes Retained by Heap";
}
@Override
- public long getSize(Instance element, Heap heap) {
- int index = mSnapshot.getHeapIndex(heap);
- return element.getRetainedSize(index);
+ public long getSize(AhatInstance element, AhatHeap heap) {
+ return element.getRetainedSize(heap);
}
@Override
- public List<HeapTable.ValueConfig<Instance>> getValueConfigs() {
- HeapTable.ValueConfig<Instance> value = new HeapTable.ValueConfig<Instance>() {
+ public List<HeapTable.ValueConfig<AhatInstance>> getValueConfigs() {
+ HeapTable.ValueConfig<AhatInstance> value = new HeapTable.ValueConfig<AhatInstance>() {
public String getDescription() {
return "Object";
}
- public DocString render(Instance element) {
- return Value.render(mSnapshot, element);
+ public DocString render(AhatInstance element) {
+ return Summarizer.summarize(element);
}
};
return Collections.singletonList(value);
diff --git a/tools/ahat/src/HeapTable.java b/tools/ahat/src/HeapTable.java
index 5b84048..9abbe4a 100644
--- a/tools/ahat/src/HeapTable.java
+++ b/tools/ahat/src/HeapTable.java
@@ -16,7 +16,9 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Heap;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Diffable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -39,21 +41,31 @@
*/
public interface TableConfig<T> {
String getHeapsDescription();
- long getSize(T element, Heap heap);
+ long getSize(T element, AhatHeap heap);
List<ValueConfig<T>> getValueConfigs();
}
+ private static DocString sizeString(long size, boolean isPlaceHolder) {
+ DocString string = new DocString();
+ if (isPlaceHolder) {
+ string.append(DocString.removed("del"));
+ } else if (size != 0) {
+ string.appendFormat("%,14d", size);
+ }
+ return string;
+ }
+
/**
* Render the table to the given document.
* @param query - The page query.
* @param id - A unique identifier for the table on the page.
*/
- public static <T> void render(Doc doc, Query query, String id,
+ public static <T extends Diffable<T>> void render(Doc doc, Query query, String id,
TableConfig<T> config, AhatSnapshot snapshot, List<T> elements) {
// Only show the heaps that have non-zero entries.
- List<Heap> heaps = new ArrayList<Heap>();
- for (Heap heap : snapshot.getHeaps()) {
- if (hasNonZeroEntry(snapshot, heap, config, elements)) {
+ List<AhatHeap> heaps = new ArrayList<AhatHeap>();
+ for (AhatHeap heap : snapshot.getHeaps()) {
+ if (hasNonZeroEntry(heap, config, elements)) {
heaps.add(heap);
}
}
@@ -61,14 +73,14 @@
List<ValueConfig<T>> values = config.getValueConfigs();
// Print the heap and values descriptions.
- boolean showTotal = heaps.size() > 1;
List<Column> subcols = new ArrayList<Column>();
- for (Heap heap : heaps) {
+ for (AhatHeap heap : heaps) {
subcols.add(new Column(heap.getName(), Column.Align.RIGHT));
+ subcols.add(new Column("Δ", Column.Align.RIGHT, snapshot.isDiffed()));
}
- if (showTotal) {
- subcols.add(new Column("Total", Column.Align.RIGHT));
- }
+ boolean showTotal = heaps.size() > 1;
+ subcols.add(new Column("Total", Column.Align.RIGHT, showTotal));
+ subcols.add(new Column("Δ", Column.Align.RIGHT, showTotal && snapshot.isDiffed()));
List<Column> cols = new ArrayList<Column>();
for (ValueConfig value : values) {
cols.add(new Column(value.getDescription()));
@@ -79,16 +91,20 @@
SubsetSelector<T> selector = new SubsetSelector(query, id, elements);
ArrayList<DocString> vals = new ArrayList<DocString>();
for (T elem : selector.selected()) {
+ T base = elem.getBaseline();
vals.clear();
long total = 0;
- for (Heap heap : heaps) {
+ long basetotal = 0;
+ for (AhatHeap heap : heaps) {
long size = config.getSize(elem, heap);
+ long basesize = config.getSize(base, heap.getBaseline());
total += size;
- vals.add(size == 0 ? DocString.text("") : DocString.format("%,14d", size));
+ basetotal += basesize;
+ vals.add(sizeString(size, elem.isPlaceHolder()));
+ vals.add(DocString.delta(elem.isPlaceHolder(), base.isPlaceHolder(), size, basesize));
}
- if (showTotal) {
- vals.add(total == 0 ? DocString.text("") : DocString.format("%,14d", total));
- }
+ vals.add(sizeString(total, elem.isPlaceHolder()));
+ vals.add(DocString.delta(elem.isPlaceHolder(), base.isPlaceHolder(), total, basetotal));
for (ValueConfig<T> value : values) {
vals.add(value.render(elem));
@@ -99,27 +115,36 @@
// Print a summary of the remaining entries if there are any.
List<T> remaining = selector.remaining();
if (!remaining.isEmpty()) {
- Map<Heap, Long> summary = new HashMap<Heap, Long>();
- for (Heap heap : heaps) {
+ Map<AhatHeap, Long> summary = new HashMap<AhatHeap, Long>();
+ Map<AhatHeap, Long> basesummary = new HashMap<AhatHeap, Long>();
+ for (AhatHeap heap : heaps) {
summary.put(heap, 0L);
+ basesummary.put(heap, 0L);
}
for (T elem : remaining) {
- for (Heap heap : heaps) {
- summary.put(heap, summary.get(heap) + config.getSize(elem, heap));
+ for (AhatHeap heap : heaps) {
+ long size = config.getSize(elem, heap);
+ summary.put(heap, summary.get(heap) + size);
+
+ long basesize = config.getSize(elem.getBaseline(), heap.getBaseline());
+ basesummary.put(heap, basesummary.get(heap) + basesize);
}
}
vals.clear();
long total = 0;
- for (Heap heap : heaps) {
+ long basetotal = 0;
+ for (AhatHeap heap : heaps) {
long size = summary.get(heap);
+ long basesize = basesummary.get(heap);
total += size;
- vals.add(DocString.format("%,14d", size));
+ basetotal += basesize;
+ vals.add(sizeString(size, false));
+ vals.add(DocString.delta(false, false, size, basesize));
}
- if (showTotal) {
- vals.add(DocString.format("%,14d", total));
- }
+ vals.add(sizeString(total, false));
+ vals.add(DocString.delta(false, false, total, basetotal));
for (ValueConfig<T> value : values) {
vals.add(DocString.text("..."));
@@ -131,11 +156,13 @@
}
// Returns true if the given heap has a non-zero size entry.
- public static <T> boolean hasNonZeroEntry(AhatSnapshot snapshot, Heap heap,
+ public static <T extends Diffable<T>> boolean hasNonZeroEntry(AhatHeap heap,
TableConfig<T> config, List<T> elements) {
- if (snapshot.getHeapSize(heap) > 0) {
+ AhatHeap baseheap = heap.getBaseline();
+ if (heap.getSize() > 0 || baseheap.getSize() > 0) {
for (T element : elements) {
- if (config.getSize(element, heap) > 0) {
+ if (config.getSize(element, heap) > 0 ||
+ config.getSize(element.getBaseline(), baseheap) > 0) {
return true;
}
}
diff --git a/tools/ahat/src/HelpHandler.java b/tools/ahat/src/HelpHandler.java
deleted file mode 100644
index 8de3c85..0000000
--- a/tools/ahat/src/HelpHandler.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.ahat;
-
-import com.google.common.io.ByteStreams;
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.HttpHandler;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintStream;
-
-/**
- * HelpHandler.
- *
- * HttpHandler to show the help page.
- */
-class HelpHandler implements HttpHandler {
-
- @Override
- public void handle(HttpExchange exchange) throws IOException {
- ClassLoader loader = HelpHandler.class.getClassLoader();
- exchange.getResponseHeaders().add("Content-Type", "text/html;charset=utf-8");
- exchange.sendResponseHeaders(200, 0);
- PrintStream ps = new PrintStream(exchange.getResponseBody());
- HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css"));
- doc.menu(Menu.getMenu());
-
- InputStream is = loader.getResourceAsStream("help.html");
- if (is == null) {
- ps.println("No help available.");
- } else {
- ByteStreams.copy(is, ps);
- }
-
- doc.close();
- ps.close();
- }
-}
diff --git a/tools/ahat/src/HtmlDoc.java b/tools/ahat/src/HtmlDoc.java
index 5ccbacb..5a22fc7 100644
--- a/tools/ahat/src/HtmlDoc.java
+++ b/tools/ahat/src/HtmlDoc.java
@@ -86,19 +86,27 @@
mCurrentTableColumns = columns;
ps.println("<table>");
for (int i = 0; i < columns.length - 1; i++) {
- ps.format("<th>%s</th>", columns[i].heading.html());
+ if (columns[i].visible) {
+ ps.format("<th>%s</th>", columns[i].heading.html());
+ }
}
// Align the last header to the left so it's easier to see if the last
// column is very wide.
- ps.format("<th align=\"left\">%s</th>", columns[columns.length - 1].heading.html());
+ if (columns[columns.length - 1].visible) {
+ ps.format("<th align=\"left\">%s</th>", columns[columns.length - 1].heading.html());
+ }
}
@Override
public void table(DocString description, List<Column> subcols, List<Column> cols) {
mCurrentTableColumns = new Column[subcols.size() + cols.size()];
int j = 0;
+ int visibleSubCols = 0;
for (Column col : subcols) {
+ if (col.visible) {
+ visibleSubCols++;
+ }
mCurrentTableColumns[j] = col;
j++;
}
@@ -108,21 +116,27 @@
}
ps.println("<table>");
- ps.format("<tr><th colspan=\"%d\">%s</th>", subcols.size(), description.html());
+ ps.format("<tr><th colspan=\"%d\">%s</th>", visibleSubCols, description.html());
for (int i = 0; i < cols.size() - 1; i++) {
- ps.format("<th rowspan=\"2\">%s</th>", cols.get(i).heading.html());
+ if (cols.get(i).visible) {
+ ps.format("<th rowspan=\"2\">%s</th>", cols.get(i).heading.html());
+ }
}
if (!cols.isEmpty()) {
// Align the last column header to the left so it can still be seen if
// the last column is very wide.
- ps.format("<th align=\"left\" rowspan=\"2\">%s</th>",
- cols.get(cols.size() - 1).heading.html());
+ Column col = cols.get(cols.size() - 1);
+ if (col.visible) {
+ ps.format("<th align=\"left\" rowspan=\"2\">%s</th>", col.heading.html());
+ }
}
ps.println("</tr>");
ps.print("<tr>");
for (Column subcol : subcols) {
- ps.format("<th>%s</th>", subcol.heading.html());
+ if (subcol.visible) {
+ ps.format("<th>%s</th>", subcol.heading.html());
+ }
}
ps.println("</tr>");
}
@@ -141,11 +155,13 @@
ps.print("<tr>");
for (int i = 0; i < values.length; i++) {
+ if (mCurrentTableColumns[i].visible) {
ps.print("<td");
- if (mCurrentTableColumns[i].align == Column.Align.RIGHT) {
- ps.print(" align=\"right\"");
+ if (mCurrentTableColumns[i].align == Column.Align.RIGHT) {
+ ps.print(" align=\"right\"");
+ }
+ ps.format(">%s</td>", values[i].html());
}
- ps.format(">%s</td>", values[i].html());
}
ps.println("</tr>");
}
diff --git a/tools/ahat/src/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java
deleted file mode 100644
index a062afd..0000000
--- a/tools/ahat/src/InstanceUtils.java
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.ArrayInstance;
-import com.android.tools.perflib.heap.ClassInstance;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Field;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.RootObj;
-import com.android.tools.perflib.heap.Type;
-
-import java.awt.image.BufferedImage;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Utilities for extracting information from hprof instances.
- */
-class InstanceUtils {
- /**
- * Returns true if the given instance is an instance of a class with the
- * given name.
- */
- private static boolean isInstanceOfClass(Instance inst, String className) {
- ClassObj cls = (inst == null) ? null : inst.getClassObj();
- return (cls != null && className.equals(cls.getClassName()));
- }
-
- /**
- * Read the byte[] value from an hprof Instance.
- * Returns null if the instance is not a byte array.
- */
- private static byte[] asByteArray(Instance inst) {
- if (!(inst instanceof ArrayInstance)) {
- return null;
- }
-
- ArrayInstance array = (ArrayInstance) inst;
- if (array.getArrayType() != Type.BYTE) {
- return null;
- }
-
- Object[] objs = array.getValues();
- byte[] bytes = new byte[objs.length];
- for (int i = 0; i < objs.length; i++) {
- Byte b = (Byte) objs[i];
- bytes[i] = b.byteValue();
- }
- return bytes;
- }
-
-
- /**
- * Read the string value from an hprof Instance.
- * Returns null if the object can't be interpreted as a string.
- */
- public static String asString(Instance inst) {
- return asString(inst, -1);
- }
-
- /**
- * Read the string value from an hprof Instance.
- * Returns null if the object can't be interpreted as a string.
- * The returned string is truncated to maxChars characters.
- * If maxChars is negative, the returned string is not truncated.
- */
- public static String asString(Instance inst, int maxChars) {
- // The inst object could either be a java.lang.String or a char[]. If it
- // is a char[], use that directly as the value, otherwise use the value
- // field of the string object. The field accesses for count and offset
- // later on will work okay regardless of what type the inst object is.
- boolean isString = isInstanceOfClass(inst, "java.lang.String");
- Object value = isString ? getField(inst, "value") : inst;
-
- if (!(value instanceof ArrayInstance)) {
- return null;
- }
-
- ArrayInstance chars = (ArrayInstance) value;
- int numChars = chars.getLength();
- int offset = getIntField(inst, "offset", 0);
- int count = getIntField(inst, "count", numChars);
-
- // With string compression enabled, the array type can be BYTE but in that case
- // offset must be 0 and count must match numChars.
- if (isString && (chars.getArrayType() == Type.BYTE) && (offset == 0) && (count == numChars)) {
- int length = (0 <= maxChars && maxChars < numChars) ? maxChars : numChars;
- return new String(chars.asRawByteArray(/* offset */ 0, length), StandardCharsets.US_ASCII);
- }
- if (chars.getArrayType() != Type.CHAR) {
- return null;
- }
- if (count == 0) {
- return "";
- }
- if (0 <= maxChars && maxChars < count) {
- count = maxChars;
- }
-
- int end = offset + count - 1;
- if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
- return new String(chars.asCharArray(offset, count));
- }
- return null;
- }
-
- /**
- * Read the bitmap data for the given android.graphics.Bitmap object.
- * Returns null if the object isn't for android.graphics.Bitmap or the
- * bitmap data couldn't be read.
- */
- public static BufferedImage asBitmap(Instance inst) {
- if (!isInstanceOfClass(inst, "android.graphics.Bitmap")) {
- return null;
- }
-
- Integer width = getIntField(inst, "mWidth", null);
- if (width == null) {
- return null;
- }
-
- Integer height = getIntField(inst, "mHeight", null);
- if (height == null) {
- return null;
- }
-
- byte[] buffer = getByteArrayField(inst, "mBuffer");
- if (buffer == null) {
- return null;
- }
-
- // Convert the raw data to an image
- // Convert BGRA to ABGR
- int[] abgr = new int[height * width];
- for (int i = 0; i < abgr.length; i++) {
- abgr[i] = (
- (((int) buffer[i * 4 + 3] & 0xFF) << 24)
- + (((int) buffer[i * 4 + 0] & 0xFF) << 16)
- + (((int) buffer[i * 4 + 1] & 0xFF) << 8)
- + ((int) buffer[i * 4 + 2] & 0xFF));
- }
-
- BufferedImage bitmap = new BufferedImage(
- width, height, BufferedImage.TYPE_4BYTE_ABGR);
- bitmap.setRGB(0, 0, width, height, abgr, 0, width);
- return bitmap;
- }
-
- /**
- * Read a field of an instance.
- * Returns null if the field value is null or if the field couldn't be read.
- */
- public static Object getField(Instance inst, String fieldName) {
- if (!(inst instanceof ClassInstance)) {
- return null;
- }
-
- ClassInstance clsinst = (ClassInstance) inst;
- Object value = null;
- int count = 0;
- for (ClassInstance.FieldValue field : clsinst.getValues()) {
- if (fieldName.equals(field.getField().getName())) {
- value = field.getValue();
- count++;
- }
- }
- return count == 1 ? value : null;
- }
-
- /**
- * Read a reference field of an instance.
- * Returns null if the field value is null, or if the field couldn't be read.
- */
- public static Instance getRefField(Instance inst, String fieldName) {
- Object value = getField(inst, fieldName);
- if (!(value instanceof Instance)) {
- return null;
- }
- return (Instance) value;
- }
-
- /**
- * Read an int field of an instance.
- * The field is assumed to be an int type.
- * Returns <code>def</code> if the field value is not an int or could not be
- * read.
- */
- private static Integer getIntField(Instance inst, String fieldName, Integer def) {
- Object value = getField(inst, fieldName);
- if (!(value instanceof Integer)) {
- return def;
- }
- return (Integer) value;
- }
-
- /**
- * Read a long field of an instance.
- * The field is assumed to be a long type.
- * Returns <code>def</code> if the field value is not an long or could not
- * be read.
- */
- private static Long getLongField(Instance inst, String fieldName, Long def) {
- Object value = getField(inst, fieldName);
- if (!(value instanceof Long)) {
- return def;
- }
- return (Long) value;
- }
-
- /**
- * Read the given field from the given instance.
- * The field is assumed to be a byte[] field.
- * Returns null if the field value is null, not a byte[] or could not be read.
- */
- private static byte[] getByteArrayField(Instance inst, String fieldName) {
- Object value = getField(inst, fieldName);
- if (!(value instanceof Instance)) {
- return null;
- }
- return asByteArray((Instance) value);
- }
-
- // Return the bitmap instance associated with this object, or null if there
- // is none. This works for android.graphics.Bitmap instances and their
- // underlying Byte[] instances.
- public static Instance getAssociatedBitmapInstance(Instance inst) {
- ClassObj cls = inst.getClassObj();
- if (cls == null) {
- return null;
- }
-
- if ("android.graphics.Bitmap".equals(cls.getClassName())) {
- return inst;
- }
-
- if (inst instanceof ArrayInstance) {
- ArrayInstance array = (ArrayInstance) inst;
- if (array.getArrayType() == Type.BYTE && inst.getHardReverseReferences().size() == 1) {
- Instance ref = inst.getHardReverseReferences().get(0);
- ClassObj clsref = ref.getClassObj();
- if (clsref != null && "android.graphics.Bitmap".equals(clsref.getClassName())) {
- return ref;
- }
- }
- }
- return null;
- }
-
- private static boolean isJavaLangRefReference(Instance inst) {
- ClassObj cls = (inst == null) ? null : inst.getClassObj();
- while (cls != null) {
- if ("java.lang.ref.Reference".equals(cls.getClassName())) {
- return true;
- }
- cls = cls.getSuperClassObj();
- }
- return false;
- }
-
- public static Instance getReferent(Instance inst) {
- if (isJavaLangRefReference(inst)) {
- return getRefField(inst, "referent");
- }
- return null;
- }
-
- /**
- * Assuming inst represents a DexCache object, return the dex location for
- * that dex cache. Returns null if the given instance doesn't represent a
- * DexCache object or the location could not be found.
- * If maxChars is non-negative, the returned location is truncated to
- * maxChars in length.
- */
- public static String getDexCacheLocation(Instance inst, int maxChars) {
- if (isInstanceOfClass(inst, "java.lang.DexCache")) {
- Instance location = getRefField(inst, "location");
- if (location != null) {
- return asString(location, maxChars);
- }
- }
- return null;
- }
-
- public static class NativeAllocation {
- public long size;
- public Heap heap;
- public long pointer;
- public Instance referent;
-
- public NativeAllocation(long size, Heap heap, long pointer, Instance referent) {
- this.size = size;
- this.heap = heap;
- this.pointer = pointer;
- this.referent = referent;
- }
- }
-
- /**
- * Assuming inst represents a NativeAllocation, return information about the
- * native allocation. Returns null if the given instance doesn't represent a
- * native allocation.
- */
- public static NativeAllocation getNativeAllocation(Instance inst) {
- if (!isInstanceOfClass(inst, "libcore.util.NativeAllocationRegistry$CleanerThunk")) {
- return null;
- }
-
- Long pointer = InstanceUtils.getLongField(inst, "nativePtr", null);
- if (pointer == null) {
- return null;
- }
-
- // Search for the registry field of inst.
- // Note: We know inst as an instance of ClassInstance because we already
- // read the nativePtr field from it.
- Instance registry = null;
- for (ClassInstance.FieldValue field : ((ClassInstance) inst).getValues()) {
- Object fieldValue = field.getValue();
- if (fieldValue instanceof Instance) {
- Instance fieldInst = (Instance) fieldValue;
- if (isInstanceOfClass(fieldInst, "libcore.util.NativeAllocationRegistry")) {
- registry = fieldInst;
- break;
- }
- }
- }
-
- if (registry == null) {
- return null;
- }
-
- Long size = InstanceUtils.getLongField(registry, "size", null);
- if (size == null) {
- return null;
- }
-
- Instance referent = null;
- for (Instance ref : inst.getHardReverseReferences()) {
- if (isInstanceOfClass(ref, "sun.misc.Cleaner")) {
- referent = InstanceUtils.getReferent(ref);
- if (referent != null) {
- break;
- }
- }
- }
-
- if (referent == null) {
- return null;
- }
- return new NativeAllocation(size, inst.getHeap(), pointer, referent);
- }
-
- public static class PathElement {
- public final Instance instance;
- public final String field;
- public boolean isDominator;
-
- public PathElement(Instance instance, String field) {
- this.instance = instance;
- this.field = field;
- this.isDominator = false;
- }
- }
-
- /**
- * Returns a sample path from a GC root to this instance.
- * The given instance is included as the last element of the path with an
- * empty field description.
- */
- public static List<PathElement> getPathFromGcRoot(Instance inst) {
- List<PathElement> path = new ArrayList<PathElement>();
-
- Instance dom = inst;
- for (PathElement elem = new PathElement(inst, ""); elem != null;
- elem = getNextPathElementToGcRoot(elem.instance)) {
- if (elem.instance == dom) {
- elem.isDominator = true;
- dom = dom.getImmediateDominator();
- }
- path.add(elem);
- }
- Collections.reverse(path);
- return path;
- }
-
- /**
- * Returns the next instance to GC root from this object and a string
- * description of which field of that object refers to the given instance.
- * Returns null if the given instance has no next instance to the gc root.
- */
- private static PathElement getNextPathElementToGcRoot(Instance inst) {
- Instance parent = inst.getNextInstanceToGcRoot();
- if (parent == null || parent instanceof RootObj) {
- return null;
- }
-
- // Search the parent for the reference to the child.
- // TODO: This seems terribly inefficient. Can we use data structures to
- // help us here?
- String description = ".???";
- if (parent instanceof ArrayInstance) {
- ArrayInstance array = (ArrayInstance)parent;
- Object[] values = array.getValues();
- for (int i = 0; i < values.length; i++) {
- if (values[i] instanceof Instance) {
- Instance ref = (Instance)values[i];
- if (ref.getId() == inst.getId()) {
- description = String.format("[%d]", i);
- break;
- }
- }
- }
- } else if (parent instanceof ClassObj) {
- ClassObj cls = (ClassObj)parent;
- for (Map.Entry<Field, Object> entries : cls.getStaticFieldValues().entrySet()) {
- if (entries.getValue() instanceof Instance) {
- Instance ref = (Instance)entries.getValue();
- if (ref.getId() == inst.getId()) {
- description = "." + entries.getKey().getName();
- break;
- }
- }
- }
- } else if (parent instanceof ClassInstance) {
- ClassInstance obj = (ClassInstance)parent;
- for (ClassInstance.FieldValue fields : obj.getValues()) {
- if (fields.getValue() instanceof Instance) {
- Instance ref = (Instance)fields.getValue();
- if (ref.getId() == inst.getId()) {
- description = "." + fields.getField().getName();
- break;
- }
- }
- }
- }
- return new PathElement(parent, description);
- }
-}
diff --git a/tools/ahat/src/Main.java b/tools/ahat/src/Main.java
index c79b578..b8552fe 100644
--- a/tools/ahat/src/Main.java
+++ b/tools/ahat/src/Main.java
@@ -16,6 +16,8 @@
package com.android.ahat;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Diff;
import com.android.tools.perflib.heap.ProguardMap;
import com.sun.net.httpserver.HttpServer;
import java.io.File;
@@ -29,15 +31,18 @@
public class Main {
public static void help(PrintStream out) {
- out.println("java -jar ahat.jar [-p port] [--proguard-map FILE] FILE");
- out.println(" Launch an http server for viewing "
- + "the given Android heap-dump FILE.");
+ out.println("java -jar ahat.jar [OPTIONS] FILE");
+ out.println(" Launch an http server for viewing the given Android heap dump FILE.");
out.println("");
- out.println("Options:");
+ out.println("OPTIONS:");
out.println(" -p <port>");
out.println(" Serve pages on the given port. Defaults to 7100.");
out.println(" --proguard-map FILE");
out.println(" Use the proguard map FILE to deobfuscate the heap dump.");
+ out.println(" --baseline FILE");
+ out.println(" Diff the heap dump against the given baseline heap dump FILE.");
+ out.println(" --baseline-proguard-map FILE");
+ out.println(" Use the proguard map FILE to deobfuscate the baseline heap dump.");
out.println("");
}
@@ -51,7 +56,9 @@
}
File hprof = null;
+ File hprofbase = null;
ProguardMap map = new ProguardMap();
+ ProguardMap mapbase = new ProguardMap();
for (int i = 0; i < args.length; i++) {
if ("-p".equals(args[i]) && i + 1 < args.length) {
i++;
@@ -64,6 +71,22 @@
System.out.println("Unable to read proguard map: " + ex);
System.out.println("The proguard map will not be used.");
}
+ } else if ("--baseline-proguard-map".equals(args[i]) && i + 1 < args.length) {
+ i++;
+ try {
+ mapbase.readFromFile(new File(args[i]));
+ } catch (IOException|ParseException ex) {
+ System.out.println("Unable to read baselline proguard map: " + ex);
+ System.out.println("The proguard map will not be used.");
+ }
+ } else if ("--baseline".equals(args[i]) && i + 1 < args.length) {
+ i++;
+ if (hprofbase != null) {
+ System.err.println("multiple baseline heap dumps.");
+ help(System.err);
+ return;
+ }
+ hprofbase = new File(args[i]);
} else {
if (hprof != null) {
System.err.println("multiple input files.");
@@ -88,17 +111,25 @@
System.out.println("Processing hprof file...");
AhatSnapshot ahat = AhatSnapshot.fromHprof(hprof, map);
- server.createContext("/", new AhatHttpHandler(new OverviewHandler(ahat, hprof)));
+
+ if (hprofbase != null) {
+ System.out.println("Processing baseline hprof file...");
+ AhatSnapshot base = AhatSnapshot.fromHprof(hprofbase, mapbase);
+
+ System.out.println("Diffing hprof files...");
+ Diff.snapshots(ahat, base);
+ }
+
+ server.createContext("/", new AhatHttpHandler(new OverviewHandler(ahat, hprof, hprofbase)));
server.createContext("/rooted", new AhatHttpHandler(new RootedHandler(ahat)));
server.createContext("/object", new AhatHttpHandler(new ObjectHandler(ahat)));
server.createContext("/objects", new AhatHttpHandler(new ObjectsHandler(ahat)));
server.createContext("/site", new AhatHttpHandler(new SiteHandler(ahat)));
- server.createContext("/native", new AhatHttpHandler(new NativeAllocationsHandler(ahat)));
server.createContext("/bitmap", new BitmapHandler(ahat));
- server.createContext("/help", new HelpHandler());
server.createContext("/style.css", new StaticHandler("style.css", "text/css"));
server.setExecutor(Executors.newFixedThreadPool(1));
System.out.println("Server started on localhost:" + port);
+
server.start();
}
}
diff --git a/tools/ahat/src/Menu.java b/tools/ahat/src/Menu.java
index 232b849..6d38dc5 100644
--- a/tools/ahat/src/Menu.java
+++ b/tools/ahat/src/Menu.java
@@ -25,11 +25,7 @@
.append(" - ")
.appendLink(DocString.uri("rooted"), DocString.text("rooted"))
.append(" - ")
- .appendLink(DocString.uri("sites"), DocString.text("allocations"))
- .append(" - ")
- .appendLink(DocString.uri("native"), DocString.text("native"))
- .append(" - ")
- .appendLink(DocString.uri("help"), DocString.text("help"));
+ .appendLink(DocString.uri("sites"), DocString.text("allocations"));
/**
* Returns the menu as a DocString.
diff --git a/tools/ahat/src/NativeAllocationsHandler.java b/tools/ahat/src/NativeAllocationsHandler.java
deleted file mode 100644
index 17407e1..0000000
--- a/tools/ahat/src/NativeAllocationsHandler.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.ahat;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-class NativeAllocationsHandler implements AhatHandler {
- private static final String ALLOCATIONS_ID = "allocations";
-
- private AhatSnapshot mSnapshot;
-
- public NativeAllocationsHandler(AhatSnapshot snapshot) {
- mSnapshot = snapshot;
- }
-
- @Override
- public void handle(Doc doc, Query query) throws IOException {
- List<InstanceUtils.NativeAllocation> allocs = mSnapshot.getNativeAllocations();
-
- doc.title("Registered Native Allocations");
-
- doc.section("Overview");
- long totalSize = 0;
- for (InstanceUtils.NativeAllocation alloc : allocs) {
- totalSize += alloc.size;
- }
- doc.descriptions();
- doc.description(DocString.text("Number of Registered Native Allocations"),
- DocString.format("%,14d", allocs.size()));
- doc.description(DocString.text("Total Size of Registered Native Allocations"),
- DocString.format("%,14d", totalSize));
- doc.end();
-
- doc.section("List of Allocations");
- if (allocs.isEmpty()) {
- doc.println(DocString.text("(none)"));
- } else {
- doc.table(
- new Column("Size", Column.Align.RIGHT),
- new Column("Heap"),
- new Column("Native Pointer"),
- new Column("Referent"));
- Comparator<InstanceUtils.NativeAllocation> compare
- = new Sort.WithPriority<InstanceUtils.NativeAllocation>(
- new Sort.NativeAllocationByHeapName(),
- new Sort.NativeAllocationBySize());
- Collections.sort(allocs, compare);
- SubsetSelector<InstanceUtils.NativeAllocation> selector
- = new SubsetSelector(query, ALLOCATIONS_ID, allocs);
- for (InstanceUtils.NativeAllocation alloc : selector.selected()) {
- doc.row(
- DocString.format("%,14d", alloc.size),
- DocString.text(alloc.heap.getName()),
- DocString.format("0x%x", alloc.pointer),
- Value.render(mSnapshot, alloc.referent));
- }
-
- // Print a summary of the remaining entries if there are any.
- List<InstanceUtils.NativeAllocation> remaining = selector.remaining();
- if (!remaining.isEmpty()) {
- long total = 0;
- for (InstanceUtils.NativeAllocation alloc : remaining) {
- total += alloc.size;
- }
-
- doc.row(
- DocString.format("%,14d", total),
- DocString.text("..."),
- DocString.text("..."),
- DocString.text("..."));
- }
-
- doc.end();
- selector.render(doc);
- }
- }
-}
-
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
index 78aac17..2e0ae6e 100644
--- a/tools/ahat/src/ObjectHandler.java
+++ b/tools/ahat/src/ObjectHandler.java
@@ -16,22 +16,23 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.ArrayInstance;
-import com.android.tools.perflib.heap.ClassInstance;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Field;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.RootType;
+import com.android.ahat.heapdump.AhatArrayInstance;
+import com.android.ahat.heapdump.AhatClassInstance;
+import com.android.ahat.heapdump.AhatClassObj;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Diff;
+import com.android.ahat.heapdump.FieldValue;
+import com.android.ahat.heapdump.PathElement;
+import com.android.ahat.heapdump.Site;
+import com.android.ahat.heapdump.Value;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
+import java.util.Objects;
-import static com.android.ahat.InstanceUtils.PathElement;
class ObjectHandler implements AhatHandler {
@@ -53,35 +54,43 @@
@Override
public void handle(Doc doc, Query query) throws IOException {
long id = query.getLong("id", 0);
- Instance inst = mSnapshot.findInstance(id);
+ AhatInstance inst = mSnapshot.findInstance(id);
if (inst == null) {
doc.println(DocString.format("No object with id %08xl", id));
return;
}
+ AhatInstance base = inst.getBaseline();
- doc.title("Object %08x", inst.getUniqueId());
- doc.big(Value.render(mSnapshot, inst));
+ doc.title("Object %08x", inst.getId());
+ doc.big(Summarizer.summarize(inst));
printAllocationSite(doc, query, inst);
printGcRootPath(doc, query, inst);
doc.section("Object Info");
- ClassObj cls = inst.getClassObj();
+ AhatClassObj cls = inst.getClassObj();
doc.descriptions();
- doc.description(DocString.text("Class"), Value.render(mSnapshot, cls));
- doc.description(DocString.text("Size"), DocString.format("%d", inst.getSize()));
- doc.description(
- DocString.text("Retained Size"),
- DocString.format("%d", inst.getTotalRetainedSize()));
+ doc.description(DocString.text("Class"), Summarizer.summarize(cls));
+
+ DocString sizeDescription = DocString.format("%,14d ", inst.getSize());
+ sizeDescription.appendDelta(false, base.isPlaceHolder(),
+ inst.getSize(), base.getSize());
+ doc.description(DocString.text("Size"), sizeDescription);
+
+ DocString rsizeDescription = DocString.format("%,14d ", inst.getTotalRetainedSize());
+ rsizeDescription.appendDelta(false, base.isPlaceHolder(),
+ inst.getTotalRetainedSize(), base.getTotalRetainedSize());
+ doc.description(DocString.text("Retained Size"), rsizeDescription);
+
doc.description(DocString.text("Heap"), DocString.text(inst.getHeap().getName()));
- Collection<RootType> rootTypes = mSnapshot.getRootTypes(inst);
+ Collection<String> rootTypes = inst.getRootTypes();
if (rootTypes != null) {
DocString types = new DocString();
String comma = "";
- for (RootType type : rootTypes) {
+ for (String type : rootTypes) {
types.append(comma);
- types.append(type.getName());
+ types.append(type);
comma = ", ";
}
doc.description(DocString.text("Root Types"), types);
@@ -90,112 +99,146 @@
doc.end();
printBitmap(doc, inst);
- if (inst instanceof ClassInstance) {
- printClassInstanceFields(doc, query, mSnapshot, (ClassInstance)inst);
- } else if (inst instanceof ArrayInstance) {
- printArrayElements(doc, query, mSnapshot, (ArrayInstance)inst);
- } else if (inst instanceof ClassObj) {
- printClassInfo(doc, query, mSnapshot, (ClassObj)inst);
+ if (inst.isClassInstance()) {
+ printClassInstanceFields(doc, query, inst.asClassInstance());
+ } else if (inst.isArrayInstance()) {
+ printArrayElements(doc, query, inst.asArrayInstance());
+ } else if (inst.isClassObj()) {
+ printClassInfo(doc, query, inst.asClassObj());
}
- printReferences(doc, query, mSnapshot, inst);
+ printReferences(doc, query, inst);
printDominatedObjects(doc, query, inst);
}
- private static void printClassInstanceFields(
- Doc doc, Query query, AhatSnapshot snapshot, ClassInstance inst) {
+ private static void printClassInstanceFields(Doc doc, Query query, AhatClassInstance inst) {
doc.section("Fields");
- doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
- SubsetSelector<ClassInstance.FieldValue> selector
- = new SubsetSelector(query, INSTANCE_FIELDS_ID, inst.getValues());
- for (ClassInstance.FieldValue field : selector.selected()) {
- doc.row(
- DocString.text(field.getField().getType().toString()),
- DocString.text(field.getField().getName()),
- Value.render(snapshot, field.getValue()));
+ AhatInstance base = inst.getBaseline();
+ List<FieldValue> fields = inst.getInstanceFields();
+ if (!base.isPlaceHolder()) {
+ Diff.fields(fields, base.asClassInstance().getInstanceFields());
}
- doc.end();
+ SubsetSelector<FieldValue> selector = new SubsetSelector(query, INSTANCE_FIELDS_ID, fields);
+ printFields(doc, inst != base && !base.isPlaceHolder(), selector.selected());
selector.render(doc);
}
- private static void printArrayElements(
- Doc doc, Query query, AhatSnapshot snapshot, ArrayInstance array) {
+ private static void printArrayElements(Doc doc, Query query, AhatArrayInstance array) {
doc.section("Array Elements");
- doc.table(new Column("Index", Column.Align.RIGHT), new Column("Value"));
- List<Object> elements = Arrays.asList(array.getValues());
- SubsetSelector<Object> selector = new SubsetSelector(query, ARRAY_ELEMENTS_ID, elements);
+ AhatInstance base = array.getBaseline();
+ boolean diff = array.getBaseline() != array && !base.isPlaceHolder();
+ doc.table(
+ new Column("Index", Column.Align.RIGHT),
+ new Column("Value"),
+ new Column("Δ", Column.Align.LEFT, diff));
+
+ List<Value> elements = array.getValues();
+ SubsetSelector<Value> selector = new SubsetSelector(query, ARRAY_ELEMENTS_ID, elements);
int i = 0;
- for (Object elem : selector.selected()) {
- doc.row(DocString.format("%d", i), Value.render(snapshot, elem));
+ for (Value current : selector.selected()) {
+ DocString delta = new DocString();
+ if (diff) {
+ Value previous = Value.getBaseline(base.asArrayInstance().getValue(i));
+ if (!Objects.equals(current, previous)) {
+ delta.append("was ");
+ delta.append(Summarizer.summarize(previous));
+ }
+ }
+ doc.row(DocString.format("%d", i), Summarizer.summarize(current), delta);
i++;
}
doc.end();
selector.render(doc);
}
- private static void printClassInfo(
- Doc doc, Query query, AhatSnapshot snapshot, ClassObj clsobj) {
+ private static void printFields(Doc doc, boolean diff, List<FieldValue> fields) {
+ doc.table(
+ new Column("Type"),
+ new Column("Name"),
+ new Column("Value"),
+ new Column("Δ", Column.Align.LEFT, diff));
+
+ for (FieldValue field : fields) {
+ Value current = field.getValue();
+ DocString value;
+ if (field.isPlaceHolder()) {
+ value = DocString.removed("del");
+ } else {
+ value = Summarizer.summarize(current);
+ }
+
+ DocString delta = new DocString();
+ FieldValue basefield = field.getBaseline();
+ if (basefield.isPlaceHolder()) {
+ delta.append(DocString.added("new"));
+ } else {
+ Value previous = Value.getBaseline(basefield.getValue());
+ if (!Objects.equals(current, previous)) {
+ delta.append("was ");
+ delta.append(Summarizer.summarize(previous));
+ }
+ }
+ doc.row(DocString.text(field.getType()), DocString.text(field.getName()), value, delta);
+ }
+ doc.end();
+ }
+
+ private static void printClassInfo(Doc doc, Query query, AhatClassObj clsobj) {
doc.section("Class Info");
doc.descriptions();
doc.description(DocString.text("Super Class"),
- Value.render(snapshot, clsobj.getSuperClassObj()));
+ Summarizer.summarize(clsobj.getSuperClassObj()));
doc.description(DocString.text("Class Loader"),
- Value.render(snapshot, clsobj.getClassLoader()));
+ Summarizer.summarize(clsobj.getClassLoader()));
doc.end();
doc.section("Static Fields");
- doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
- List<Map.Entry<Field, Object>> fields
- = new ArrayList<Map.Entry<Field, Object>>(clsobj.getStaticFieldValues().entrySet());
- SubsetSelector<Map.Entry<Field, Object>> selector
- = new SubsetSelector(query, STATIC_FIELDS_ID, fields);
- for (Map.Entry<Field, Object> field : selector.selected()) {
- doc.row(
- DocString.text(field.getKey().getType().toString()),
- DocString.text(field.getKey().getName()),
- Value.render(snapshot, field.getValue()));
+ AhatInstance base = clsobj.getBaseline();
+ List<FieldValue> fields = clsobj.getStaticFieldValues();
+ if (!base.isPlaceHolder()) {
+ Diff.fields(fields, base.asClassObj().getStaticFieldValues());
}
- doc.end();
+ SubsetSelector<FieldValue> selector = new SubsetSelector(query, STATIC_FIELDS_ID, fields);
+ printFields(doc, clsobj != base && !base.isPlaceHolder(), selector.selected());
selector.render(doc);
}
- private static void printReferences(
- Doc doc, Query query, AhatSnapshot snapshot, Instance inst) {
+ private static void printReferences(Doc doc, Query query, AhatInstance inst) {
doc.section("Objects with References to this Object");
if (inst.getHardReverseReferences().isEmpty()) {
doc.println(DocString.text("(none)"));
} else {
doc.table(new Column("Object"));
- List<Instance> references = inst.getHardReverseReferences();
- SubsetSelector<Instance> selector = new SubsetSelector(query, HARD_REFS_ID, references);
- for (Instance ref : selector.selected()) {
- doc.row(Value.render(snapshot, ref));
+ List<AhatInstance> references = inst.getHardReverseReferences();
+ SubsetSelector<AhatInstance> selector = new SubsetSelector(query, HARD_REFS_ID, references);
+ for (AhatInstance ref : selector.selected()) {
+ doc.row(Summarizer.summarize(ref));
}
doc.end();
selector.render(doc);
}
- if (inst.getSoftReverseReferences() != null) {
+ if (!inst.getSoftReverseReferences().isEmpty()) {
doc.section("Objects with Soft References to this Object");
doc.table(new Column("Object"));
- List<Instance> references = inst.getSoftReverseReferences();
- SubsetSelector<Instance> selector = new SubsetSelector(query, SOFT_REFS_ID, references);
- for (Instance ref : selector.selected()) {
- doc.row(Value.render(snapshot, ref));
+ List<AhatInstance> references = inst.getSoftReverseReferences();
+ SubsetSelector<AhatInstance> selector = new SubsetSelector(query, SOFT_REFS_ID, references);
+ for (AhatInstance ref : selector.selected()) {
+ doc.row(Summarizer.summarize(ref));
}
doc.end();
selector.render(doc);
}
}
- private void printAllocationSite(Doc doc, Query query, Instance inst) {
+ private void printAllocationSite(Doc doc, Query query, AhatInstance inst) {
doc.section("Allocation Site");
- Site site = mSnapshot.getSiteForInstance(inst);
+ Site site = inst.getSite();
SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
}
// Draw the bitmap corresponding to this instance if there is one.
- private static void printBitmap(Doc doc, Instance inst) {
- Instance bitmap = InstanceUtils.getAssociatedBitmapInstance(inst);
+ private static void printBitmap(Doc doc, AhatInstance inst) {
+ AhatInstance bitmap = inst.getAssociatedBitmapInstance();
if (bitmap != null) {
doc.section("Bitmap Image");
doc.println(DocString.image(
@@ -203,25 +246,25 @@
}
}
- private void printGcRootPath(Doc doc, Query query, Instance inst) {
+ private void printGcRootPath(Doc doc, Query query, AhatInstance inst) {
doc.section("Sample Path from GC Root");
- List<PathElement> path = InstanceUtils.getPathFromGcRoot(inst);
+ List<PathElement> path = inst.getPathFromGcRoot();
- // Add 'null' as a marker for the root.
- path.add(0, null);
+ // Add a dummy PathElement as a marker for the root.
+ final PathElement root = new PathElement(null, null);
+ path.add(0, root);
HeapTable.TableConfig<PathElement> table = new HeapTable.TableConfig<PathElement>() {
public String getHeapsDescription() {
return "Bytes Retained by Heap (Dominators Only)";
}
- public long getSize(PathElement element, Heap heap) {
- if (element == null) {
- return mSnapshot.getHeapSize(heap);
+ public long getSize(PathElement element, AhatHeap heap) {
+ if (element == root) {
+ return heap.getSize();
}
if (element.isDominator) {
- int index = mSnapshot.getHeapIndex(heap);
- return element.instance.getRetainedSize(index);
+ return element.instance.getRetainedSize(heap);
}
return 0;
}
@@ -233,11 +276,11 @@
}
public DocString render(PathElement element) {
- if (element == null) {
+ if (element == root) {
return DocString.link(DocString.uri("rooted"), DocString.text("ROOT"));
} else {
- DocString label = DocString.text(" → ");
- label.append(Value.render(mSnapshot, element.instance));
+ DocString label = DocString.text("→ ");
+ label.append(Summarizer.summarize(element.instance));
label.append(element.field);
return label;
}
@@ -249,9 +292,9 @@
HeapTable.render(doc, query, DOMINATOR_PATH_ID, table, mSnapshot, path);
}
- public void printDominatedObjects(Doc doc, Query query, Instance inst) {
+ public void printDominatedObjects(Doc doc, Query query, AhatInstance inst) {
doc.section("Immediately Dominated Objects");
- List<Instance> instances = mSnapshot.getDominated(inst);
+ List<AhatInstance> instances = inst.getDominated();
if (instances != null) {
DominatedList.render(mSnapshot, doc, query, DOMINATED_OBJECTS_ID, instances);
} else {
diff --git a/tools/ahat/src/ObjectsHandler.java b/tools/ahat/src/ObjectsHandler.java
index 4cfb0a5..3062d23 100644
--- a/tools/ahat/src/ObjectsHandler.java
+++ b/tools/ahat/src/ObjectsHandler.java
@@ -16,7 +16,10 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Site;
+import com.android.ahat.heapdump.Sort;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@@ -33,17 +36,16 @@
@Override
public void handle(Doc doc, Query query) throws IOException {
- int stackId = query.getInt("stack", 0);
+ int id = query.getInt("id", 0);
int depth = query.getInt("depth", 0);
String className = query.get("class", null);
String heapName = query.get("heap", null);
- Site site = mSnapshot.getSite(stackId, depth);
+ Site site = mSnapshot.getSite(id, depth);
- List<Instance> insts = new ArrayList<Instance>();
- for (Instance inst : site.getObjects()) {
+ List<AhatInstance> insts = new ArrayList<AhatInstance>();
+ for (AhatInstance inst : site.getObjects()) {
if ((heapName == null || inst.getHeap().getName().equals(heapName))
- && (className == null
- || AhatSnapshot.getClassName(inst.getClassObj()).equals(className))) {
+ && (className == null || inst.getClassName().equals(className))) {
insts.add(inst);
}
}
@@ -51,16 +53,22 @@
Collections.sort(insts, Sort.defaultInstanceCompare(mSnapshot));
doc.title("Objects");
+
doc.table(
new Column("Size", Column.Align.RIGHT),
+ new Column("Δ", Column.Align.RIGHT, mSnapshot.isDiffed()),
new Column("Heap"),
new Column("Object"));
- SubsetSelector<Instance> selector = new SubsetSelector(query, OBJECTS_ID, insts);
- for (Instance inst : selector.selected()) {
+
+ SubsetSelector<AhatInstance> selector = new SubsetSelector(query, OBJECTS_ID, insts);
+ for (AhatInstance inst : selector.selected()) {
+ AhatInstance base = inst.getBaseline();
doc.row(
- DocString.format("%,d", inst.getSize()),
+ DocString.format("%,14d", inst.getSize()),
+ DocString.delta(inst.isPlaceHolder(), base.isPlaceHolder(),
+ inst.getSize(), base.getSize()),
DocString.text(inst.getHeap().getName()),
- Value.render(mSnapshot, inst));
+ Summarizer.summarize(inst));
}
doc.end();
selector.render(doc);
diff --git a/tools/ahat/src/OverviewHandler.java b/tools/ahat/src/OverviewHandler.java
index 0dbad7e..ea305c4 100644
--- a/tools/ahat/src/OverviewHandler.java
+++ b/tools/ahat/src/OverviewHandler.java
@@ -16,9 +16,11 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Heap;
-import java.io.IOException;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Diffable;
import java.io.File;
+import java.io.IOException;
import java.util.Collections;
import java.util.List;
@@ -28,10 +30,12 @@
private AhatSnapshot mSnapshot;
private File mHprof;
+ private File mBaseHprof;
- public OverviewHandler(AhatSnapshot snapshot, File hprof) {
+ public OverviewHandler(AhatSnapshot snapshot, File hprof, File basehprof) {
mSnapshot = snapshot;
mHprof = hprof;
+ mBaseHprof = basehprof;
}
@Override
@@ -44,42 +48,40 @@
DocString.text("ahat version"),
DocString.format("ahat-%s", OverviewHandler.class.getPackage().getImplementationVersion()));
doc.description(DocString.text("hprof file"), DocString.text(mHprof.toString()));
+ if (mBaseHprof != null) {
+ doc.description(DocString.text("baseline hprof file"), DocString.text(mBaseHprof.toString()));
+ }
doc.end();
doc.section("Heap Sizes");
printHeapSizes(doc, query);
- List<InstanceUtils.NativeAllocation> allocs = mSnapshot.getNativeAllocations();
- if (!allocs.isEmpty()) {
- doc.section("Registered Native Allocations");
- long totalSize = 0;
- for (InstanceUtils.NativeAllocation alloc : allocs) {
- totalSize += alloc.size;
- }
- doc.descriptions();
- doc.description(DocString.text("Number of Registered Native Allocations"),
- DocString.format("%,14d", allocs.size()));
- doc.description(DocString.text("Total Size of Registered Native Allocations"),
- DocString.format("%,14d", totalSize));
- doc.end();
- }
-
doc.big(Menu.getMenu());
}
- private void printHeapSizes(Doc doc, Query query) {
- List<Object> dummy = Collections.singletonList(null);
+ private static class TableElem implements Diffable<TableElem> {
+ @Override public TableElem getBaseline() {
+ return this;
+ }
- HeapTable.TableConfig<Object> table = new HeapTable.TableConfig<Object>() {
+ @Override public boolean isPlaceHolder() {
+ return false;
+ }
+ }
+
+ private void printHeapSizes(Doc doc, Query query) {
+ List<TableElem> dummy = Collections.singletonList(new TableElem());
+
+ HeapTable.TableConfig<TableElem> table = new HeapTable.TableConfig<TableElem>() {
public String getHeapsDescription() {
return "Bytes Retained by Heap";
}
- public long getSize(Object element, Heap heap) {
- return mSnapshot.getHeapSize(heap);
+ public long getSize(TableElem element, AhatHeap heap) {
+ return heap.getSize();
}
- public List<HeapTable.ValueConfig<Object>> getValueConfigs() {
+ public List<HeapTable.ValueConfig<TableElem>> getValueConfigs() {
return Collections.emptyList();
}
};
diff --git a/tools/ahat/src/RootedHandler.java b/tools/ahat/src/RootedHandler.java
index ec3272f..26451a3 100644
--- a/tools/ahat/src/RootedHandler.java
+++ b/tools/ahat/src/RootedHandler.java
@@ -16,6 +16,7 @@
package com.android.ahat;
+import com.android.ahat.heapdump.AhatSnapshot;
import java.io.IOException;
class RootedHandler implements AhatHandler {
diff --git a/tools/ahat/src/Site.java b/tools/ahat/src/Site.java
deleted file mode 100644
index dbb84f6..0000000
--- a/tools/ahat/src/Site.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.StackFrame;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-class Site {
- // The site that this site was directly called from.
- // mParent is null for the root site.
- private Site mParent;
-
- // A description of the Site. Currently this is used to uniquely identify a
- // site within its parent.
- private String mName;
-
- // To identify this site, we pick one stack trace where we have seen the
- // site. mStackId is the id for that stack trace, and mStackDepth is the
- // depth of this site in that stack trace.
- // For the root site, mStackId is 0 and mStackDepth is 0.
- private int mStackId;
- private int mStackDepth;
-
- // Mapping from heap name to the total size of objects allocated in this
- // site (including child sites) on the given heap.
- private Map<String, Long> mSizesByHeap;
-
- // Mapping from child site name to child site.
- private Map<String, Site> mChildren;
-
- // List of all objects allocated in this site (including child sites).
- private List<Instance> mObjects;
- private List<ObjectsInfo> mObjectsInfos;
- private Map<Heap, Map<ClassObj, ObjectsInfo>> mObjectsInfoMap;
-
- public static class ObjectsInfo {
- public Heap heap;
- public ClassObj classObj;
- public long numInstances;
- public long numBytes;
-
- public ObjectsInfo(Heap heap, ClassObj classObj, long numInstances, long numBytes) {
- this.heap = heap;
- this.classObj = classObj;
- this.numInstances = numInstances;
- this.numBytes = numBytes;
- }
- }
-
- /**
- * Construct a root site.
- */
- public Site(String name) {
- this(null, name, 0, 0);
- }
-
- public Site(Site parent, String name, int stackId, int stackDepth) {
- mParent = parent;
- mName = name;
- mStackId = stackId;
- mStackDepth = stackDepth;
- mSizesByHeap = new HashMap<String, Long>();
- mChildren = new HashMap<String, Site>();
- mObjects = new ArrayList<Instance>();
- mObjectsInfos = new ArrayList<ObjectsInfo>();
- mObjectsInfoMap = new HashMap<Heap, Map<ClassObj, ObjectsInfo>>();
- }
-
- /**
- * Add an instance to this site.
- * Returns the site at which the instance was allocated.
- */
- public Site add(int stackId, int stackDepth, Iterator<StackFrame> path, Instance inst) {
- mObjects.add(inst);
-
- String heap = inst.getHeap().getName();
- mSizesByHeap.put(heap, getSize(heap) + inst.getSize());
-
- Map<ClassObj, ObjectsInfo> classToObjectsInfo = mObjectsInfoMap.get(inst.getHeap());
- if (classToObjectsInfo == null) {
- classToObjectsInfo = new HashMap<ClassObj, ObjectsInfo>();
- mObjectsInfoMap.put(inst.getHeap(), classToObjectsInfo);
- }
-
- ObjectsInfo info = classToObjectsInfo.get(inst.getClassObj());
- if (info == null) {
- info = new ObjectsInfo(inst.getHeap(), inst.getClassObj(), 0, 0);
- mObjectsInfos.add(info);
- classToObjectsInfo.put(inst.getClassObj(), info);
- }
-
- info.numInstances++;
- info.numBytes += inst.getSize();
-
- if (path.hasNext()) {
- String next = path.next().toString();
- Site child = mChildren.get(next);
- if (child == null) {
- child = new Site(this, next, stackId, stackDepth + 1);
- mChildren.put(next, child);
- }
- return child.add(stackId, stackDepth + 1, path, inst);
- } else {
- return this;
- }
- }
-
- // Get the size of a site for a specific heap.
- public long getSize(String heap) {
- Long val = mSizesByHeap.get(heap);
- if (val == null) {
- return 0;
- }
- return val;
- }
-
- /**
- * Get the list of objects allocated under this site. Includes objects
- * allocated in children sites.
- */
- public Collection<Instance> getObjects() {
- return mObjects;
- }
-
- public List<ObjectsInfo> getObjectsInfos() {
- return mObjectsInfos;
- }
-
- // Get the combined size of the site for all heaps.
- public long getTotalSize() {
- long size = 0;
- for (Long val : mSizesByHeap.values()) {
- size += val;
- }
- return size;
- }
-
- /**
- * Return the site this site was called from.
- * Returns null for the root site.
- */
- public Site getParent() {
- return mParent;
- }
-
- public String getName() {
- return mName;
- }
-
- // Returns the hprof id of a stack this site appears on.
- public int getStackId() {
- return mStackId;
- }
-
- // Returns the stack depth of this site in the stack whose id is returned
- // by getStackId().
- public int getStackDepth() {
- return mStackDepth;
- }
-
- List<Site> getChildren() {
- return new ArrayList<Site>(mChildren.values());
- }
-
- // Get the child at the given path relative to this site.
- // Returns null if no such child found.
- Site getChild(Iterator<StackFrame> path) {
- if (path.hasNext()) {
- String next = path.next().toString();
- Site child = mChildren.get(next);
- return (child == null) ? null : child.getChild(path);
- } else {
- return this;
- }
- }
-}
diff --git a/tools/ahat/src/SiteHandler.java b/tools/ahat/src/SiteHandler.java
index 839e220..febf171 100644
--- a/tools/ahat/src/SiteHandler.java
+++ b/tools/ahat/src/SiteHandler.java
@@ -16,7 +16,10 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Heap;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Site;
+import com.android.ahat.heapdump.Sort;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
@@ -35,11 +38,13 @@
@Override
public void handle(Doc doc, Query query) throws IOException {
- int stackId = query.getInt("stack", 0);
- int depth = query.getInt("depth", -1);
- Site site = mSnapshot.getSite(stackId, depth);
+ int id = query.getInt("id", 0);
+ int depth = query.getInt("depth", 0);
+ Site site = mSnapshot.getSite(id, depth);
- doc.title("Site %s", site.getName());
+ doc.title("Site");
+ doc.big(Summarizer.summarize(site));
+
doc.section("Allocation Site");
SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
@@ -48,15 +53,14 @@
if (children.isEmpty()) {
doc.println(DocString.text("(none)"));
} else {
- Collections.sort(children, new Sort.SiteBySize("app"));
-
+ Collections.sort(children, Sort.defaultSiteCompare(mSnapshot));
HeapTable.TableConfig<Site> table = new HeapTable.TableConfig<Site>() {
public String getHeapsDescription() {
return "Reachable Bytes Allocated on Heap";
}
- public long getSize(Site element, Heap heap) {
- return element.getSize(heap.getName());
+ public long getSize(Site element, AhatHeap heap) {
+ return element.getSize(heap);
}
public List<HeapTable.ValueConfig<Site>> getValueConfigs() {
@@ -66,10 +70,7 @@
}
public DocString render(Site element) {
- return DocString.link(
- DocString.formattedUri("site?stack=%d&depth=%d",
- element.getStackId(), element.getStackDepth()),
- DocString.text(element.getName()));
+ return Summarizer.summarize(element);
}
};
return Collections.singletonList(value);
@@ -79,29 +80,36 @@
}
doc.section("Objects Allocated");
+
doc.table(
new Column("Reachable Bytes Allocated", Column.Align.RIGHT),
+ new Column("Δ", Column.Align.RIGHT, mSnapshot.isDiffed()),
new Column("Instances", Column.Align.RIGHT),
+ new Column("Δ", Column.Align.RIGHT, mSnapshot.isDiffed()),
new Column("Heap"),
new Column("Class"));
+
List<Site.ObjectsInfo> infos = site.getObjectsInfos();
Comparator<Site.ObjectsInfo> compare = new Sort.WithPriority<Site.ObjectsInfo>(
- new Sort.ObjectsInfoByHeapName(),
- new Sort.ObjectsInfoBySize(),
- new Sort.ObjectsInfoByClassName());
+ Sort.OBJECTS_INFO_BY_HEAP_NAME,
+ Sort.OBJECTS_INFO_BY_SIZE,
+ Sort.OBJECTS_INFO_BY_CLASS_NAME);
Collections.sort(infos, compare);
SubsetSelector<Site.ObjectsInfo> selector
= new SubsetSelector(query, OBJECTS_ALLOCATED_ID, infos);
for (Site.ObjectsInfo info : selector.selected()) {
- String className = AhatSnapshot.getClassName(info.classObj);
+ Site.ObjectsInfo baseinfo = info.getBaseline();
+ String className = info.getClassName();
doc.row(
DocString.format("%,14d", info.numBytes),
+ DocString.delta(false, false, info.numBytes, baseinfo.numBytes),
DocString.link(
- DocString.formattedUri("objects?stack=%d&depth=%d&heap=%s&class=%s",
- site.getStackId(), site.getStackDepth(), info.heap.getName(), className),
+ DocString.formattedUri("objects?id=%d&depth=%d&heap=%s&class=%s",
+ site.getId(), site.getDepth(), info.heap.getName(), className),
DocString.format("%,14d", info.numInstances)),
+ DocString.delta(false, false, info.numInstances, baseinfo.numInstances),
DocString.text(info.heap.getName()),
- Value.render(mSnapshot, info.classObj));
+ Summarizer.summarize(info.classObj));
}
doc.end();
selector.render(doc);
diff --git a/tools/ahat/src/SitePrinter.java b/tools/ahat/src/SitePrinter.java
index 2c06b47..21ca2de 100644
--- a/tools/ahat/src/SitePrinter.java
+++ b/tools/ahat/src/SitePrinter.java
@@ -16,7 +16,9 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Heap;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Site;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -35,8 +37,8 @@
return "Reachable Bytes Allocated on Heap";
}
- public long getSize(Site element, Heap heap) {
- return element.getSize(heap.getName());
+ public long getSize(Site element, AhatHeap heap) {
+ return element.getSize(heap);
}
public List<HeapTable.ValueConfig<Site>> getValueConfigs() {
@@ -50,11 +52,7 @@
if (element.getParent() != null) {
str.append("→ ");
}
- str.appendLink(
- DocString.formattedUri("site?stack=%d&depth=%d",
- element.getStackId(), element.getStackDepth()),
- DocString.text(element.getName()));
- return str;
+ return str.append(Summarizer.summarize(element));
}
};
return Collections.singletonList(value);
diff --git a/tools/ahat/src/Sort.java b/tools/ahat/src/Sort.java
deleted file mode 100644
index 8a3d9f2..0000000
--- a/tools/ahat/src/Sort.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Provides Comparators and helper functions for sorting Instances, Sites, and
- * other things.
- *
- * Note: The Comparators defined here impose orderings that are inconsistent
- * with equals. They should not be used for element lookup or search. They
- * should only be used for showing elements to the user in different orders.
- */
-class Sort {
- /**
- * Compare instances by their instance id.
- * This sorts instances from smaller id to larger id.
- */
- public static class InstanceById implements Comparator<Instance> {
- @Override
- public int compare(Instance a, Instance b) {
- return Long.compare(a.getId(), b.getId());
- }
- }
-
- /**
- * Compare instances by their total retained size.
- * Different instances with the same total retained size are considered
- * equal for the purposes of comparison.
- * This sorts instances from larger retained size to smaller retained size.
- */
- public static class InstanceByTotalRetainedSize implements Comparator<Instance> {
- @Override
- public int compare(Instance a, Instance b) {
- return Long.compare(b.getTotalRetainedSize(), a.getTotalRetainedSize());
- }
- }
-
- /**
- * Compare instances by their retained size for a given heap index.
- * Different instances with the same total retained size are considered
- * equal for the purposes of comparison.
- * This sorts instances from larger retained size to smaller retained size.
- */
- public static class InstanceByHeapRetainedSize implements Comparator<Instance> {
- private int mIndex;
-
- public InstanceByHeapRetainedSize(AhatSnapshot snapshot, Heap heap) {
- mIndex = snapshot.getHeapIndex(heap);
- }
-
- public InstanceByHeapRetainedSize(int heapIndex) {
- mIndex = heapIndex;
- }
-
- @Override
- public int compare(Instance a, Instance b) {
- return Long.compare(b.getRetainedSize(mIndex), a.getRetainedSize(mIndex));
- }
- }
-
- /**
- * Compare objects based on a list of comparators, giving priority to the
- * earlier comparators in the list.
- */
- public static class WithPriority<T> implements Comparator<T> {
- private List<Comparator<T>> mComparators;
-
- public WithPriority(Comparator<T>... comparators) {
- mComparators = Arrays.asList(comparators);
- }
-
- public WithPriority(List<Comparator<T>> comparators) {
- mComparators = comparators;
- }
-
- @Override
- public int compare(T a, T b) {
- int res = 0;
- Iterator<Comparator<T>> iter = mComparators.iterator();
- while (res == 0 && iter.hasNext()) {
- res = iter.next().compare(a, b);
- }
- return res;
- }
- }
-
- public static Comparator<Instance> defaultInstanceCompare(AhatSnapshot snapshot) {
- List<Comparator<Instance>> comparators = new ArrayList<Comparator<Instance>>();
-
- // Priority goes to the app heap, if we can find one.
- Heap appHeap = snapshot.getHeap("app");
- if (appHeap != null) {
- comparators.add(new InstanceByHeapRetainedSize(snapshot, appHeap));
- }
-
- // Next is by total retained size.
- comparators.add(new InstanceByTotalRetainedSize());
- return new WithPriority<Instance>(comparators);
- }
-
- /**
- * Compare Sites by the size of objects allocated on a given heap.
- * Different object infos with the same size on the given heap are
- * considered equal for the purposes of comparison.
- * This sorts sites from larger size to smaller size.
- */
- public static class SiteBySize implements Comparator<Site> {
- String mHeap;
-
- public SiteBySize(String heap) {
- mHeap = heap;
- }
-
- @Override
- public int compare(Site a, Site b) {
- return Long.compare(b.getSize(mHeap), a.getSize(mHeap));
- }
- }
-
- /**
- * Compare Site.ObjectsInfo by their size.
- * Different object infos with the same total retained size are considered
- * equal for the purposes of comparison.
- * This sorts object infos from larger retained size to smaller size.
- */
- public static class ObjectsInfoBySize implements Comparator<Site.ObjectsInfo> {
- @Override
- public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
- return Long.compare(b.numBytes, a.numBytes);
- }
- }
-
- /**
- * Compare Site.ObjectsInfo by heap name.
- * Different object infos with the same heap name are considered equal for
- * the purposes of comparison.
- */
- public static class ObjectsInfoByHeapName implements Comparator<Site.ObjectsInfo> {
- @Override
- public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
- return a.heap.getName().compareTo(b.heap.getName());
- }
- }
-
- /**
- * Compare Site.ObjectsInfo by class name.
- * Different object infos with the same class name are considered equal for
- * the purposes of comparison.
- */
- public static class ObjectsInfoByClassName implements Comparator<Site.ObjectsInfo> {
- @Override
- public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
- String aName = AhatSnapshot.getClassName(a.classObj);
- String bName = AhatSnapshot.getClassName(b.classObj);
- return aName.compareTo(bName);
- }
- }
-
- /**
- * Compare AhatSnapshot.NativeAllocation by heap name.
- * Different allocations with the same heap name are considered equal for
- * the purposes of comparison.
- */
- public static class NativeAllocationByHeapName
- implements Comparator<InstanceUtils.NativeAllocation> {
- @Override
- public int compare(InstanceUtils.NativeAllocation a, InstanceUtils.NativeAllocation b) {
- return a.heap.getName().compareTo(b.heap.getName());
- }
- }
-
- /**
- * Compare InstanceUtils.NativeAllocation by their size.
- * Different allocations with the same size are considered equal for the
- * purposes of comparison.
- * This sorts allocations from larger size to smaller size.
- */
- public static class NativeAllocationBySize implements Comparator<InstanceUtils.NativeAllocation> {
- @Override
- public int compare(InstanceUtils.NativeAllocation a, InstanceUtils.NativeAllocation b) {
- return Long.compare(b.size, a.size);
- }
- }
-}
-
diff --git a/tools/ahat/src/StaticHandler.java b/tools/ahat/src/StaticHandler.java
index fb7049d..b2805d6 100644
--- a/tools/ahat/src/StaticHandler.java
+++ b/tools/ahat/src/StaticHandler.java
@@ -17,10 +17,10 @@
package com.android.ahat;
import com.google.common.io.ByteStreams;
-import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
-import java.io.InputStream;
+import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
diff --git a/tools/ahat/src/Summarizer.java b/tools/ahat/src/Summarizer.java
new file mode 100644
index 0000000..016eab4
--- /dev/null
+++ b/tools/ahat/src/Summarizer.java
@@ -0,0 +1,143 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.Site;
+import com.android.ahat.heapdump.Value;
+import java.net.URI;
+
+/**
+ * Class for generating a DocString summary of an instance or value.
+ */
+class Summarizer {
+
+ // For string literals, we limit the number of characters we show to
+ // kMaxChars in case the string is really long.
+ private static int kMaxChars = 200;
+
+ /**
+ * Creates a DocString representing a summary of the given instance.
+ */
+ public static DocString summarize(AhatInstance inst) {
+ DocString formatted = new DocString();
+ if (inst == null) {
+ formatted.append("null");
+ return formatted;
+ }
+
+ // Annotate new objects as new.
+ if (inst.getBaseline().isPlaceHolder()) {
+ formatted.append(DocString.added("new "));
+ }
+
+ // Annotate deleted objects as deleted.
+ if (inst.isPlaceHolder()) {
+ formatted.append(DocString.removed("del "));
+ }
+
+ // Annotate unreachable objects as such.
+ if (!inst.isReachable()) {
+ formatted.append("unreachable ");
+ }
+
+ // Annotate roots as roots.
+ if (inst.isRoot()) {
+ formatted.append("root ");
+ }
+
+ // Annotate classes as classes.
+ DocString linkText = new DocString();
+ if (inst.isClassObj()) {
+ linkText.append("class ");
+ }
+
+ linkText.append(inst.toString());
+
+ if (inst.isPlaceHolder()) {
+ // Don't make links to placeholder objects.
+ formatted.append(linkText);
+ } else {
+ URI objTarget = DocString.formattedUri("object?id=%d", inst.getId());
+ formatted.appendLink(objTarget, linkText);
+ }
+
+ // Annotate Strings with their values.
+ String stringValue = inst.asString(kMaxChars);
+ if (stringValue != null) {
+ formatted.appendFormat(" \"%s", stringValue);
+ formatted.append(kMaxChars == stringValue.length() ? "..." : "\"");
+ }
+
+ // Annotate Reference with its referent
+ AhatInstance referent = inst.getReferent();
+ if (referent != null) {
+ formatted.append(" for ");
+
+ // It should not be possible for a referent to refer back to the
+ // reference object, even indirectly, so there shouldn't be any issues
+ // with infinite recursion here.
+ formatted.append(summarize(referent));
+ }
+
+ // Annotate DexCache with its location.
+ String dexCacheLocation = inst.getDexCacheLocation(kMaxChars);
+ if (dexCacheLocation != null) {
+ formatted.appendFormat(" for %s", dexCacheLocation);
+ if (kMaxChars == dexCacheLocation.length()) {
+ formatted.append("...");
+ }
+ }
+
+ // Annotate bitmaps with a thumbnail.
+ AhatInstance bitmap = inst.getAssociatedBitmapInstance();
+ String thumbnail = "";
+ if (bitmap != null) {
+ URI uri = DocString.formattedUri("bitmap?id=%d", bitmap.getId());
+ formatted.appendThumbnail(uri, "bitmap image");
+ }
+ return formatted;
+ }
+
+ /**
+ * Creates a DocString summarizing the given value.
+ */
+ public static DocString summarize(Value value) {
+ if (value == null) {
+ return DocString.text("null");
+ }
+ if (value.isAhatInstance()) {
+ return summarize(value.asAhatInstance());
+ }
+ return DocString.text(value.toString());
+ }
+
+ /**
+ * Creates a DocString summarizing the given site.
+ */
+ public static DocString summarize(Site site) {
+ DocString text = DocString.text(site.getMethodName());
+ text.append(site.getSignature());
+ text.append(" - ");
+ text.append(site.getFilename());
+ if (site.getLineNumber() > 0) {
+ text.append(":").append(Integer.toString(site.getLineNumber()));
+ }
+ URI uri = DocString.formattedUri("site?id=%d&depth=%d", site.getId(), site.getDepth());
+ return DocString.link(uri, text);
+ }
+}
diff --git a/tools/ahat/src/Value.java b/tools/ahat/src/Value.java
deleted file mode 100644
index 847692b..0000000
--- a/tools/ahat/src/Value.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Instance;
-import java.net.URI;
-
-/**
- * Class to render an hprof value to a DocString.
- */
-class Value {
-
- // For string literals, we limit the number of characters we show to
- // kMaxChars in case the string is really long.
- private static int kMaxChars = 200;
-
- /**
- * Create a DocString representing a summary of the given instance.
- */
- private static DocString renderInstance(AhatSnapshot snapshot, Instance inst) {
- DocString formatted = new DocString();
- if (inst == null) {
- formatted.append("(null)");
- return formatted;
- }
-
- // Annotate roots as roots.
- if (snapshot.isRoot(inst)) {
- formatted.append("(root) ");
- }
-
-
- // Annotate classes as classes.
- DocString link = new DocString();
- if (inst instanceof ClassObj) {
- link.append("class ");
- }
-
- link.append(inst.toString());
-
- URI objTarget = DocString.formattedUri("object?id=%d", inst.getId());
- formatted.appendLink(objTarget, link);
-
- // Annotate Strings with their values.
- String stringValue = InstanceUtils.asString(inst, kMaxChars);
- if (stringValue != null) {
- formatted.appendFormat(" \"%s", stringValue);
- formatted.append(kMaxChars == stringValue.length() ? "..." : "\"");
- }
-
- // Annotate Reference with its referent
- Instance referent = InstanceUtils.getReferent(inst);
- if (referent != null) {
- formatted.append(" for ");
-
- // It should not be possible for a referent to refer back to the
- // reference object, even indirectly, so there shouldn't be any issues
- // with infinite recursion here.
- formatted.append(renderInstance(snapshot, referent));
- }
-
- // Annotate DexCache with its location.
- String dexCacheLocation = InstanceUtils.getDexCacheLocation(inst, kMaxChars);
- if (dexCacheLocation != null) {
- formatted.appendFormat(" for %s", dexCacheLocation);
- if (kMaxChars == dexCacheLocation.length()) {
- formatted.append("...");
- }
- }
-
-
- // Annotate bitmaps with a thumbnail.
- Instance bitmap = InstanceUtils.getAssociatedBitmapInstance(inst);
- String thumbnail = "";
- if (bitmap != null) {
- URI uri = DocString.formattedUri("bitmap?id=%d", bitmap.getId());
- formatted.appendThumbnail(uri, "bitmap image");
- }
- return formatted;
- }
-
- /**
- * Create a DocString summarizing the given value.
- */
- public static DocString render(AhatSnapshot snapshot, Object val) {
- if (val instanceof Instance) {
- return renderInstance(snapshot, (Instance)val);
- } else {
- return DocString.format("%s", val);
- }
- }
-}
diff --git a/tools/ahat/src/heapdump/AhatArrayInstance.java b/tools/ahat/src/heapdump/AhatArrayInstance.java
new file mode 100644
index 0000000..d88cf94
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatArrayInstance.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.ArrayInstance;
+import com.android.tools.perflib.heap.Instance;
+import java.nio.charset.StandardCharsets;
+import java.util.AbstractList;
+import java.util.List;
+
+public class AhatArrayInstance extends AhatInstance {
+ // To save space, we store byte, character, and object arrays directly as
+ // byte, character, and AhatInstance arrays respectively. This is especially
+ // important for large byte arrays, such as bitmaps. All other array types
+ // are stored as an array of objects, though we could potentially save space
+ // by specializing those too. mValues is a list view of the underlying
+ // array.
+ private List<Value> mValues;
+ private byte[] mByteArray; // null if not a byte array.
+ private char[] mCharArray; // null if not a char array.
+
+ public AhatArrayInstance(long id) {
+ super(id);
+ }
+
+ @Override void initialize(AhatSnapshot snapshot, Instance inst) {
+ super.initialize(snapshot, inst);
+
+ ArrayInstance array = (ArrayInstance)inst;
+ switch (array.getArrayType()) {
+ case OBJECT:
+ Object[] objects = array.getValues();
+ final AhatInstance[] insts = new AhatInstance[objects.length];
+ for (int i = 0; i < objects.length; i++) {
+ if (objects[i] != null) {
+ Instance ref = (Instance)objects[i];
+ insts[i] = snapshot.findInstance(ref.getId());
+ if (ref.getNextInstanceToGcRoot() == inst) {
+ String field = "[" + Integer.toString(i) + "]";
+ insts[i].setNextInstanceToGcRoot(this, field);
+ }
+ }
+ }
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return insts.length;
+ }
+
+ @Override public Value get(int index) {
+ AhatInstance obj = insts[index];
+ return obj == null ? null : new Value(insts[index]);
+ }
+ };
+ break;
+
+ case CHAR:
+ final char[] chars = array.asCharArray(0, array.getLength());
+ mCharArray = chars;
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return chars.length;
+ }
+
+ @Override public Value get(int index) {
+ return new Value(chars[index]);
+ }
+ };
+ break;
+
+ case BYTE:
+ final byte[] bytes = array.asRawByteArray(0, array.getLength());
+ mByteArray = bytes;
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return bytes.length;
+ }
+
+ @Override public Value get(int index) {
+ return new Value(bytes[index]);
+ }
+ };
+ break;
+
+ default:
+ final Object[] values = array.getValues();
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return values.length;
+ }
+
+ @Override public Value get(int index) {
+ Object obj = values[index];
+ return obj == null ? null : new Value(obj);
+ }
+ };
+ break;
+ }
+ }
+
+ /**
+ * Returns the length of the array.
+ */
+ public int getLength() {
+ return mValues.size();
+ }
+
+ /**
+ * Returns the array's values.
+ */
+ public List<Value> getValues() {
+ return mValues;
+ }
+
+ /**
+ * Returns the object at the given index of this array.
+ */
+ public Value getValue(int index) {
+ return mValues.get(index);
+ }
+
+ @Override public boolean isArrayInstance() {
+ return true;
+ }
+
+ @Override public AhatArrayInstance asArrayInstance() {
+ return this;
+ }
+
+ @Override public String asString(int maxChars) {
+ return asString(0, getLength(), maxChars);
+ }
+
+ /**
+ * Returns the String value associated with this array.
+ * Only char arrays are considered as having an associated String value.
+ */
+ String asString(int offset, int count, int maxChars) {
+ if (mCharArray == null) {
+ return null;
+ }
+
+ if (count == 0) {
+ return "";
+ }
+ int numChars = mCharArray.length;
+ if (0 <= maxChars && maxChars < count) {
+ count = maxChars;
+ }
+
+ int end = offset + count - 1;
+ if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
+ return new String(mCharArray, offset, count);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the ascii String value associated with this array.
+ * Only byte arrays are considered as having an associated ascii String value.
+ */
+ String asAsciiString(int offset, int count, int maxChars) {
+ if (mByteArray == null) {
+ return null;
+ }
+
+ if (count == 0) {
+ return "";
+ }
+ int numChars = mByteArray.length;
+ if (0 <= maxChars && maxChars < count) {
+ count = maxChars;
+ }
+
+ int end = offset + count - 1;
+ if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
+ return new String(mByteArray, offset, count, StandardCharsets.US_ASCII);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the String value associated with this array. Byte arrays are
+ * considered as ascii encoded strings.
+ */
+ String asMaybeCompressedString(int offset, int count, int maxChars) {
+ String str = asString(offset, count, maxChars);
+ if (str == null) {
+ str = asAsciiString(offset, count, maxChars);
+ }
+ return str;
+ }
+
+ @Override public AhatInstance getAssociatedBitmapInstance() {
+ if (mByteArray != null) {
+ List<AhatInstance> refs = getHardReverseReferences();
+ if (refs.size() == 1) {
+ AhatInstance ref = refs.get(0);
+ return ref.getAssociatedBitmapInstance();
+ }
+ }
+ return null;
+ }
+
+ @Override public String toString() {
+ String className = getClassName();
+ if (className.endsWith("[]")) {
+ className = className.substring(0, className.length() - 2);
+ }
+ return String.format("%s[%d]@%08x", className, mValues.size(), getId());
+ }
+
+ byte[] asByteArray() {
+ return mByteArray;
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatClassInstance.java b/tools/ahat/src/heapdump/AhatClassInstance.java
new file mode 100644
index 0000000..273530a
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatClassInstance.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.ClassInstance;
+import com.android.tools.perflib.heap.Instance;
+import java.awt.image.BufferedImage;
+import java.util.Arrays;
+import java.util.List;
+
+public class AhatClassInstance extends AhatInstance {
+ private FieldValue[] mFieldValues;
+
+ public AhatClassInstance(long id) {
+ super(id);
+ }
+
+ @Override void initialize(AhatSnapshot snapshot, Instance inst) {
+ super.initialize(snapshot, inst);
+
+ ClassInstance classInst = (ClassInstance)inst;
+ List<ClassInstance.FieldValue> fieldValues = classInst.getValues();
+ mFieldValues = new FieldValue[fieldValues.size()];
+ for (int i = 0; i < mFieldValues.length; i++) {
+ ClassInstance.FieldValue field = fieldValues.get(i);
+ String name = field.getField().getName();
+ String type = field.getField().getType().toString();
+ Value value = snapshot.getValue(field.getValue());
+
+ mFieldValues[i] = new FieldValue(name, type, value);
+
+ if (field.getValue() instanceof Instance) {
+ Instance ref = (Instance)field.getValue();
+ if (ref.getNextInstanceToGcRoot() == inst) {
+ value.asAhatInstance().setNextInstanceToGcRoot(this, "." + name);
+ }
+ }
+ }
+ }
+
+ @Override public Value getField(String fieldName) {
+ for (FieldValue field : mFieldValues) {
+ if (fieldName.equals(field.getName())) {
+ return field.getValue();
+ }
+ }
+ return null;
+ }
+
+ @Override public AhatInstance getRefField(String fieldName) {
+ Value value = getField(fieldName);
+ return value == null ? null : value.asAhatInstance();
+ }
+
+ /**
+ * Read an int field of an instance.
+ * The field is assumed to be an int type.
+ * Returns <code>def</code> if the field value is not an int or could not be
+ * read.
+ */
+ private Integer getIntField(String fieldName, Integer def) {
+ Value value = getField(fieldName);
+ if (value == null || !value.isInteger()) {
+ return def;
+ }
+ return value.asInteger();
+ }
+
+ /**
+ * Read a long field of this instance.
+ * The field is assumed to be a long type.
+ * Returns <code>def</code> if the field value is not an long or could not
+ * be read.
+ */
+ private Long getLongField(String fieldName, Long def) {
+ Value value = getField(fieldName);
+ if (value == null || !value.isLong()) {
+ return def;
+ }
+ return value.asLong();
+ }
+
+ /**
+ * Returns the list of class instance fields for this instance.
+ */
+ public List<FieldValue> getInstanceFields() {
+ return Arrays.asList(mFieldValues);
+ }
+
+ /**
+ * Returns true if this is an instance of a class with the given name.
+ */
+ private boolean isInstanceOfClass(String className) {
+ AhatClassObj cls = getClassObj();
+ while (cls != null) {
+ if (className.equals(cls.getName())) {
+ return true;
+ }
+ cls = cls.getSuperClassObj();
+ }
+ return false;
+ }
+
+ @Override public String asString(int maxChars) {
+ if (!isInstanceOfClass("java.lang.String")) {
+ return null;
+ }
+
+ Value value = getField("value");
+ if (!value.isAhatInstance()) {
+ return null;
+ }
+
+ AhatInstance inst = value.asAhatInstance();
+ if (inst.isArrayInstance()) {
+ AhatArrayInstance chars = inst.asArrayInstance();
+ int numChars = chars.getLength();
+ int count = getIntField("count", numChars);
+ int offset = getIntField("offset", 0);
+ return chars.asMaybeCompressedString(offset, count, maxChars);
+ }
+ return null;
+ }
+
+ @Override public AhatInstance getReferent() {
+ if (isInstanceOfClass("java.lang.ref.Reference")) {
+ return getRefField("referent");
+ }
+ return null;
+ }
+
+ @Override public String getDexCacheLocation(int maxChars) {
+ if (isInstanceOfClass("java.lang.DexCache")) {
+ AhatInstance location = getRefField("location");
+ if (location != null) {
+ return location.asString(maxChars);
+ }
+ }
+ return null;
+ }
+
+ @Override public AhatInstance getAssociatedBitmapInstance() {
+ if (isInstanceOfClass("android.graphics.Bitmap")) {
+ return this;
+ }
+ return null;
+ }
+
+ @Override public boolean isClassInstance() {
+ return true;
+ }
+
+ @Override public AhatClassInstance asClassInstance() {
+ return this;
+ }
+
+ @Override public String toString() {
+ return String.format("%s@%08x", getClassName(), getId());
+ }
+
+ /**
+ * Read the given field from the given instance.
+ * The field is assumed to be a byte[] field.
+ * Returns null if the field value is null, not a byte[] or could not be read.
+ */
+ private byte[] getByteArrayField(String fieldName) {
+ Value value = getField(fieldName);
+ if (!value.isAhatInstance()) {
+ return null;
+ }
+ return value.asAhatInstance().asByteArray();
+ }
+
+ public BufferedImage asBitmap() {
+ if (!isInstanceOfClass("android.graphics.Bitmap")) {
+ return null;
+ }
+
+ Integer width = getIntField("mWidth", null);
+ if (width == null) {
+ return null;
+ }
+
+ Integer height = getIntField("mHeight", null);
+ if (height == null) {
+ return null;
+ }
+
+ byte[] buffer = getByteArrayField("mBuffer");
+ if (buffer == null) {
+ return null;
+ }
+
+ // Convert the raw data to an image
+ // Convert BGRA to ABGR
+ int[] abgr = new int[height * width];
+ for (int i = 0; i < abgr.length; i++) {
+ abgr[i] = (
+ (((int) buffer[i * 4 + 3] & 0xFF) << 24)
+ + (((int) buffer[i * 4 + 0] & 0xFF) << 16)
+ + (((int) buffer[i * 4 + 1] & 0xFF) << 8)
+ + ((int) buffer[i * 4 + 2] & 0xFF));
+ }
+
+ BufferedImage bitmap = new BufferedImage(
+ width, height, BufferedImage.TYPE_4BYTE_ABGR);
+ bitmap.setRGB(0, 0, width, height, abgr, 0, width);
+ return bitmap;
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatClassObj.java b/tools/ahat/src/heapdump/AhatClassObj.java
new file mode 100644
index 0000000..c5ade1d
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatClassObj.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Field;
+import com.android.tools.perflib.heap.Instance;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+public class AhatClassObj extends AhatInstance {
+ private String mClassName;
+ private AhatClassObj mSuperClassObj;
+ private AhatInstance mClassLoader;
+ private FieldValue[] mStaticFieldValues;
+
+ public AhatClassObj(long id) {
+ super(id);
+ }
+
+ @Override void initialize(AhatSnapshot snapshot, Instance inst) {
+ super.initialize(snapshot, inst);
+
+ ClassObj classObj = (ClassObj)inst;
+ mClassName = classObj.getClassName();
+
+ ClassObj superClassObj = classObj.getSuperClassObj();
+ if (superClassObj != null) {
+ mSuperClassObj = snapshot.findClassObj(superClassObj.getId());
+ }
+
+ Instance loader = classObj.getClassLoader();
+ if (loader != null) {
+ mClassLoader = snapshot.findInstance(loader.getId());
+ }
+
+ Collection<Map.Entry<Field, Object>> fieldValues = classObj.getStaticFieldValues().entrySet();
+ mStaticFieldValues = new FieldValue[fieldValues.size()];
+ int index = 0;
+ for (Map.Entry<Field, Object> field : fieldValues) {
+ String name = field.getKey().getName();
+ String type = field.getKey().getType().toString();
+ Value value = snapshot.getValue(field.getValue());
+ mStaticFieldValues[index++] = new FieldValue(name, type, value);
+
+ if (field.getValue() instanceof Instance) {
+ Instance ref = (Instance)field.getValue();
+ if (ref.getNextInstanceToGcRoot() == inst) {
+ value.asAhatInstance().setNextInstanceToGcRoot(this, "." + name);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the name of the class this is a class object for.
+ */
+ public String getName() {
+ return mClassName;
+ }
+
+ /**
+ * Returns the superclass of this class object.
+ */
+ public AhatClassObj getSuperClassObj() {
+ return mSuperClassObj;
+ }
+
+ /**
+ * Returns the class loader of this class object.
+ */
+ public AhatInstance getClassLoader() {
+ return mClassLoader;
+ }
+
+ /**
+ * Returns the static field values for this class object.
+ */
+ public List<FieldValue> getStaticFieldValues() {
+ return Arrays.asList(mStaticFieldValues);
+ }
+
+ @Override public boolean isClassObj() {
+ return true;
+ }
+
+ @Override public AhatClassObj asClassObj() {
+ return this;
+ }
+
+ @Override public String toString() {
+ return mClassName;
+ }
+
+ @Override AhatInstance newPlaceHolderInstance() {
+ return new AhatPlaceHolderClassObj(this);
+ }
+}
+
diff --git a/tools/ahat/src/heapdump/AhatField.java b/tools/ahat/src/heapdump/AhatField.java
new file mode 100644
index 0000000..a25ee28
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatField.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat.heapdump;
+
+public class AhatField {
+ private final String mName;
+ private final String mType;
+
+ public AhatField(String name, String type) {
+ mName = name;
+ mType = type;
+ }
+
+ /**
+ * Returns the name of the field.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns a description of the type of the field.
+ */
+ public String getType() {
+ return mType;
+ }
+}
+
diff --git a/tools/ahat/src/heapdump/AhatHeap.java b/tools/ahat/src/heapdump/AhatHeap.java
new file mode 100644
index 0000000..c39adc4
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatHeap.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat.heapdump;
+
+public class AhatHeap implements Diffable<AhatHeap> {
+ private String mName;
+ private long mSize = 0;
+ private int mIndex;
+ private AhatHeap mBaseline;
+ private boolean mIsPlaceHolder = false;
+
+ AhatHeap(String name, int index) {
+ mName = name;
+ mIndex = index;
+ mBaseline = this;
+ }
+
+ /**
+ * Construct a place holder heap.
+ */
+ private AhatHeap(String name, AhatHeap baseline) {
+ mName = name;
+ mIndex = -1;
+ mBaseline = baseline;
+ baseline.setBaseline(this);
+ mIsPlaceHolder = true;
+ }
+
+ /**
+ * Construct a new place holder heap that has the given baseline heap.
+ */
+ static AhatHeap newPlaceHolderHeap(String name, AhatHeap baseline) {
+ return new AhatHeap(name, baseline);
+ }
+
+ void addToSize(long increment) {
+ mSize += increment;
+ }
+
+ /**
+ * Returns a unique instance for this heap between 0 and the total number of
+ * heaps in this snapshot, or -1 if this is a placeholder heap.
+ */
+ int getIndex() {
+ return mIndex;
+ }
+
+ /**
+ * Returns the name of this heap.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the total number of bytes allocated on this heap.
+ */
+ public long getSize() {
+ return mSize;
+ }
+
+ void setBaseline(AhatHeap baseline) {
+ mBaseline = baseline;
+ }
+
+ @Override
+ public AhatHeap getBaseline() {
+ return mBaseline;
+ }
+
+ @Override
+ public boolean isPlaceHolder() {
+ return mIsPlaceHolder;
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatInstance.java b/tools/ahat/src/heapdump/AhatInstance.java
new file mode 100644
index 0000000..e6b9c00
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatInstance.java
@@ -0,0 +1,455 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.RootObj;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class AhatInstance implements Diffable<AhatInstance> {
+ private long mId;
+ private long mSize;
+ private long mTotalRetainedSize;
+ private long mRetainedSizes[]; // Retained size indexed by heap index
+ private boolean mIsReachable;
+ private AhatHeap mHeap;
+ private AhatInstance mImmediateDominator;
+ private AhatInstance mNextInstanceToGcRoot;
+ private String mNextInstanceToGcRootField = "???";
+ private AhatClassObj mClassObj;
+ private AhatInstance[] mHardReverseReferences;
+ private AhatInstance[] mSoftReverseReferences;
+ private Site mSite;
+
+ // If this instance is a root, mRootTypes contains a set of the root types.
+ // If this instance is not a root, mRootTypes is null.
+ private List<String> mRootTypes;
+
+ // List of instances this instance immediately dominates.
+ private List<AhatInstance> mDominated = new ArrayList<AhatInstance>();
+
+ private AhatInstance mBaseline;
+
+ public AhatInstance(long id) {
+ mId = id;
+ mBaseline = this;
+ }
+
+ /**
+ * Initializes this AhatInstance based on the given perflib instance.
+ * The AhatSnapshot should be used to look up AhatInstances and AhatHeaps.
+ * There is no guarantee that the AhatInstances returned by
+ * snapshot.findInstance have been initialized yet.
+ */
+ void initialize(AhatSnapshot snapshot, Instance inst) {
+ mId = inst.getId();
+ mSize = inst.getSize();
+ mTotalRetainedSize = inst.getTotalRetainedSize();
+ mIsReachable = inst.isReachable();
+
+ List<AhatHeap> heaps = snapshot.getHeaps();
+ mRetainedSizes = new long[heaps.size()];
+ for (AhatHeap heap : heaps) {
+ mRetainedSizes[heap.getIndex()] = inst.getRetainedSize(heap.getIndex());
+ }
+
+ mHeap = snapshot.getHeap(inst.getHeap().getName());
+
+ Instance dom = inst.getImmediateDominator();
+ if (dom == null || dom instanceof RootObj) {
+ mImmediateDominator = null;
+ } else {
+ mImmediateDominator = snapshot.findInstance(dom.getId());
+ mImmediateDominator.mDominated.add(this);
+ }
+
+ ClassObj clsObj = inst.getClassObj();
+ if (clsObj != null) {
+ mClassObj = snapshot.findClassObj(clsObj.getId());
+ }
+
+ // A couple notes about reverse references:
+ // * perflib sometimes returns unreachable reverse references. If
+ // snapshot.findInstance returns null, it means the reverse reference is
+ // not reachable, so we filter it out.
+ // * We store the references as AhatInstance[] instead of
+ // ArrayList<AhatInstance> because it saves a lot of space and helps
+ // with performance when there are a lot of AhatInstances.
+ ArrayList<AhatInstance> ahatRefs = new ArrayList<AhatInstance>();
+ ahatRefs = new ArrayList<AhatInstance>();
+ for (Instance ref : inst.getHardReverseReferences()) {
+ AhatInstance ahat = snapshot.findInstance(ref.getId());
+ if (ahat != null) {
+ ahatRefs.add(ahat);
+ }
+ }
+ mHardReverseReferences = new AhatInstance[ahatRefs.size()];
+ ahatRefs.toArray(mHardReverseReferences);
+
+ List<Instance> refs = inst.getSoftReverseReferences();
+ ahatRefs.clear();
+ if (refs != null) {
+ for (Instance ref : refs) {
+ AhatInstance ahat = snapshot.findInstance(ref.getId());
+ if (ahat != null) {
+ ahatRefs.add(ahat);
+ }
+ }
+ }
+ mSoftReverseReferences = new AhatInstance[ahatRefs.size()];
+ ahatRefs.toArray(mSoftReverseReferences);
+ }
+
+ /**
+ * Returns a unique identifier for the instance.
+ */
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the shallow number of bytes this object takes up.
+ */
+ public long getSize() {
+ return mSize;
+ }
+
+ /**
+ * Returns the number of bytes belonging to the given heap that this instance
+ * retains.
+ */
+ public long getRetainedSize(AhatHeap heap) {
+ int index = heap.getIndex();
+ return 0 <= index && index < mRetainedSizes.length ? mRetainedSizes[heap.getIndex()] : 0;
+ }
+
+ /**
+ * Returns the total number of bytes this instance retains.
+ */
+ public long getTotalRetainedSize() {
+ return mTotalRetainedSize;
+ }
+
+ /**
+ * Returns whether this object is strongly-reachable.
+ */
+ public boolean isReachable() {
+ return mIsReachable;
+ }
+
+ /**
+ * Returns the heap that this instance is allocated on.
+ */
+ public AhatHeap getHeap() {
+ return mHeap;
+ }
+
+ /**
+ * Returns true if this instance is marked as a root instance.
+ */
+ public boolean isRoot() {
+ return mRootTypes != null;
+ }
+
+ /**
+ * Marks this instance as being a root of the given type.
+ */
+ void addRootType(String type) {
+ if (mRootTypes == null) {
+ mRootTypes = new ArrayList<String>();
+ mRootTypes.add(type);
+ } else if (!mRootTypes.contains(type)) {
+ mRootTypes.add(type);
+ }
+ }
+
+ /**
+ * Returns a list of string descriptions of the root types of this object.
+ * Returns null if this object is not a root.
+ */
+ public Collection<String> getRootTypes() {
+ return mRootTypes;
+ }
+
+ /**
+ * Returns the immediate dominator of this instance.
+ * Returns null if this is a root instance.
+ */
+ public AhatInstance getImmediateDominator() {
+ return mImmediateDominator;
+ }
+
+ /**
+ * Returns a list of those objects immediately dominated by the given
+ * instance.
+ */
+ public List<AhatInstance> getDominated() {
+ return mDominated;
+ }
+
+ /**
+ * Returns the site where this instance was allocated.
+ */
+ public Site getSite() {
+ return mSite;
+ }
+
+ /**
+ * Sets the allocation site of this instance.
+ */
+ void setSite(Site site) {
+ mSite = site;
+ }
+
+ /**
+ * Returns true if the given instance is a class object
+ */
+ public boolean isClassObj() {
+ // Overridden by AhatClassObj.
+ return false;
+ }
+
+ /**
+ * Returns this as an AhatClassObj if this is an AhatClassObj.
+ * Returns null if this is not an AhatClassObj.
+ */
+ public AhatClassObj asClassObj() {
+ // Overridden by AhatClassObj.
+ return null;
+ }
+
+ /**
+ * Returns the class object instance for the class of this object.
+ */
+ public AhatClassObj getClassObj() {
+ return mClassObj;
+ }
+
+ /**
+ * Returns the name of the class this object belongs to.
+ */
+ public String getClassName() {
+ AhatClassObj classObj = getClassObj();
+ return classObj == null ? "???" : classObj.getName();
+ }
+
+ /**
+ * Returns true if the given instance is an array instance
+ */
+ public boolean isArrayInstance() {
+ // Overridden by AhatArrayInstance.
+ return false;
+ }
+
+ /**
+ * Returns this as an AhatArrayInstance if this is an AhatArrayInstance.
+ * Returns null if this is not an AhatArrayInstance.
+ */
+ public AhatArrayInstance asArrayInstance() {
+ // Overridden by AhatArrayInstance.
+ return null;
+ }
+
+ /**
+ * Returns true if the given instance is a class instance
+ */
+ public boolean isClassInstance() {
+ return false;
+ }
+
+ /**
+ * Returns this as an AhatClassInstance if this is an AhatClassInstance.
+ * Returns null if this is not an AhatClassInstance.
+ */
+ public AhatClassInstance asClassInstance() {
+ return null;
+ }
+
+ /**
+ * Return the referent associated with this instance.
+ * This is relevent for instances of java.lang.ref.Reference.
+ * Returns null if the instance has no referent associated with it.
+ */
+ public AhatInstance getReferent() {
+ // Overridden by AhatClassInstance.
+ return null;
+ }
+
+ /**
+ * Returns a list of objects with hard references to this object.
+ */
+ public List<AhatInstance> getHardReverseReferences() {
+ return Arrays.asList(mHardReverseReferences);
+ }
+
+ /**
+ * Returns a list of objects with soft references to this object.
+ */
+ public List<AhatInstance> getSoftReverseReferences() {
+ return Arrays.asList(mSoftReverseReferences);
+ }
+
+ /**
+ * Returns the value of a field of an instance.
+ * Returns null if the field value is null, the field couldn't be read, or
+ * there are multiple fields with the same name.
+ */
+ public Value getField(String fieldName) {
+ // Overridden by AhatClassInstance.
+ return null;
+ }
+
+ /**
+ * Reads a reference field of this instance.
+ * Returns null if the field value is null, or if the field couldn't be read.
+ */
+ public AhatInstance getRefField(String fieldName) {
+ // Overridden by AhatClassInstance.
+ return null;
+ }
+
+ /**
+ * Assuming inst represents a DexCache object, return the dex location for
+ * that dex cache. Returns null if the given instance doesn't represent a
+ * DexCache object or the location could not be found.
+ * If maxChars is non-negative, the returned location is truncated to
+ * maxChars in length.
+ */
+ public String getDexCacheLocation(int maxChars) {
+ return null;
+ }
+
+ /**
+ * Return the bitmap instance associated with this object, or null if there
+ * is none. This works for android.graphics.Bitmap instances and their
+ * underlying Byte[] instances.
+ */
+ public AhatInstance getAssociatedBitmapInstance() {
+ return null;
+ }
+
+ /**
+ * Read the string value from this instance.
+ * Returns null if this object can't be interpreted as a string.
+ * The returned string is truncated to maxChars characters.
+ * If maxChars is negative, the returned string is not truncated.
+ */
+ public String asString(int maxChars) {
+ // By default instances can't be interpreted as a string. This method is
+ // overridden by AhatClassInstance and AhatArrayInstance for those cases
+ // when an instance can be interpreted as a string.
+ return null;
+ }
+
+ /**
+ * Reads the string value from an hprof Instance.
+ * Returns null if the object can't be interpreted as a string.
+ */
+ public String asString() {
+ return asString(-1);
+ }
+
+ /**
+ * Return the bitmap associated with the given instance, if any.
+ * This is relevant for instances of android.graphics.Bitmap and byte[].
+ * Returns null if there is no bitmap associated with the given instance.
+ */
+ public BufferedImage asBitmap() {
+ return null;
+ }
+
+ /**
+ * Returns a sample path from a GC root to this instance.
+ * This instance is included as the last element of the path with an empty
+ * field description.
+ */
+ public List<PathElement> getPathFromGcRoot() {
+ List<PathElement> path = new ArrayList<PathElement>();
+
+ AhatInstance dom = this;
+ for (PathElement elem = new PathElement(this, ""); elem != null;
+ elem = getNextPathElementToGcRoot(elem.instance)) {
+ if (elem.instance.equals(dom)) {
+ elem.isDominator = true;
+ dom = dom.getImmediateDominator();
+ }
+ path.add(elem);
+ }
+ Collections.reverse(path);
+ return path;
+ }
+
+ /**
+ * Returns the next instance to GC root from this object and a string
+ * description of which field of that object refers to the given instance.
+ * Returns null if the given instance has no next instance to the gc root.
+ */
+ private static PathElement getNextPathElementToGcRoot(AhatInstance inst) {
+ AhatInstance parent = inst.mNextInstanceToGcRoot;
+ if (parent == null) {
+ return null;
+ }
+ return new PathElement(inst.mNextInstanceToGcRoot, inst.mNextInstanceToGcRootField);
+ }
+
+ void setNextInstanceToGcRoot(AhatInstance inst, String field) {
+ mNextInstanceToGcRoot = inst;
+ mNextInstanceToGcRootField = field;
+ }
+
+ /** Returns a human-readable identifier for this object.
+ * For class objects, the string is the class name.
+ * For class instances, the string is the class name followed by '@' and the
+ * hex id of the instance.
+ * For array instances, the string is the array type followed by the size in
+ * square brackets, followed by '@' and the hex id of the instance.
+ */
+ @Override public abstract String toString();
+
+ /**
+ * Read the byte[] value from an hprof Instance.
+ * Returns null if the instance is not a byte array.
+ */
+ byte[] asByteArray() {
+ return null;
+ }
+
+ public void setBaseline(AhatInstance baseline) {
+ mBaseline = baseline;
+ }
+
+ @Override public AhatInstance getBaseline() {
+ return mBaseline;
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return false;
+ }
+
+ /**
+ * Returns a new place holder instance corresponding to this instance.
+ */
+ AhatInstance newPlaceHolderInstance() {
+ return new AhatPlaceHolderInstance(this);
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
new file mode 100644
index 0000000..c6ad87f
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat.heapdump;
+
+/**
+ * PlaceHolder instance to take the place of a real AhatClassObj for
+ * the purposes of displaying diffs.
+ *
+ * This should be created through a call to newPlaceHolder();
+ */
+public class AhatPlaceHolderClassObj extends AhatClassObj {
+ AhatPlaceHolderClassObj(AhatClassObj baseline) {
+ super(-1);
+ setBaseline(baseline);
+ baseline.setBaseline(this);
+ }
+
+ @Override public long getSize() {
+ return 0;
+ }
+
+ @Override public long getRetainedSize(AhatHeap heap) {
+ return 0;
+ }
+
+ @Override public long getTotalRetainedSize() {
+ return 0;
+ }
+
+ @Override public AhatHeap getHeap() {
+ return getBaseline().getHeap().getBaseline();
+ }
+
+ @Override public String getClassName() {
+ return getBaseline().getClassName();
+ }
+
+ @Override public String toString() {
+ return getBaseline().toString();
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return true;
+ }
+
+ @Override public String getName() {
+ return getBaseline().asClassObj().getName();
+ }
+
+ @Override public AhatClassObj getSuperClassObj() {
+ return getBaseline().asClassObj().getSuperClassObj().getBaseline().asClassObj();
+ }
+
+ @Override public AhatInstance getClassLoader() {
+ return getBaseline().asClassObj().getClassLoader().getBaseline();
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
new file mode 100644
index 0000000..9412eae
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat.heapdump;
+
+/**
+ * Generic PlaceHolder instance to take the place of a real AhatInstance for
+ * the purposes of displaying diffs.
+ *
+ * This should be created through a call to AhatInstance.newPlaceHolder();
+ */
+public class AhatPlaceHolderInstance extends AhatInstance {
+ AhatPlaceHolderInstance(AhatInstance baseline) {
+ super(-1);
+ setBaseline(baseline);
+ baseline.setBaseline(this);
+ }
+
+ @Override public long getSize() {
+ return 0;
+ }
+
+ @Override public long getRetainedSize(AhatHeap heap) {
+ return 0;
+ }
+
+ @Override public long getTotalRetainedSize() {
+ return 0;
+ }
+
+ @Override public AhatHeap getHeap() {
+ return getBaseline().getHeap().getBaseline();
+ }
+
+ @Override public String getClassName() {
+ return getBaseline().getClassName();
+ }
+
+ @Override public String asString(int maxChars) {
+ return getBaseline().asString(maxChars);
+ }
+
+ @Override public String toString() {
+ return getBaseline().toString();
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return true;
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatSnapshot.java b/tools/ahat/src/heapdump/AhatSnapshot.java
new file mode 100644
index 0000000..20b85da
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatSnapshot.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.captures.DataBuffer;
+import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
+import com.android.tools.perflib.heap.ArrayInstance;
+import com.android.tools.perflib.heap.ClassInstance;
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.ProguardMap;
+import com.android.tools.perflib.heap.RootObj;
+import com.android.tools.perflib.heap.Snapshot;
+import com.android.tools.perflib.heap.StackFrame;
+import com.android.tools.perflib.heap.StackTrace;
+import gnu.trove.TObjectProcedure;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class AhatSnapshot implements Diffable<AhatSnapshot> {
+ private final Site mRootSite = new Site("ROOT");
+
+ // Collection of objects whose immediate dominator is the SENTINEL_ROOT.
+ private final List<AhatInstance> mRooted = new ArrayList<AhatInstance>();
+
+ // List of all ahat instances stored in increasing order by id.
+ private final List<AhatInstance> mInstances = new ArrayList<AhatInstance>();
+
+ // Map from class name to class object.
+ private final Map<String, AhatClassObj> mClasses = new HashMap<String, AhatClassObj>();
+
+ private final List<AhatHeap> mHeaps = new ArrayList<AhatHeap>();
+
+ private AhatSnapshot mBaseline = this;
+
+ /**
+ * Create an AhatSnapshot from an hprof file.
+ */
+ public static AhatSnapshot fromHprof(File hprof, ProguardMap map) throws IOException {
+ return fromDataBuffer(new MemoryMappedFileBuffer(hprof), map);
+ }
+
+ /**
+ * Create an AhatSnapshot from an in-memory data buffer.
+ */
+ public static AhatSnapshot fromDataBuffer(DataBuffer buffer, ProguardMap map) throws IOException {
+ AhatSnapshot snapshot = new AhatSnapshot(buffer, map);
+
+ // Request a GC now to clean up memory used by perflib. This helps to
+ // avoid a noticable pause when visiting the first interesting page in
+ // ahat.
+ System.gc();
+
+ return snapshot;
+ }
+
+ /**
+ * Constructs an AhatSnapshot for the given hprof binary data.
+ */
+ private AhatSnapshot(DataBuffer buffer, ProguardMap map) throws IOException {
+ Snapshot snapshot = Snapshot.createSnapshot(buffer, map);
+ snapshot.computeDominators();
+
+ // Properly label the class of class objects in the perflib snapshot, and
+ // count the total number of instances.
+ final ClassObj javaLangClass = snapshot.findClass("java.lang.Class");
+ if (javaLangClass != null) {
+ for (Heap heap : snapshot.getHeaps()) {
+ Collection<ClassObj> classes = heap.getClasses();
+ for (ClassObj clsObj : classes) {
+ if (clsObj.getClassObj() == null) {
+ clsObj.setClassId(javaLangClass.getId());
+ }
+ }
+ }
+ }
+
+ // Create mappings from id to ahat instance and heaps.
+ Collection<Heap> heaps = snapshot.getHeaps();
+ for (Heap heap : heaps) {
+ // Note: mHeaps will not be in index order if snapshot.getHeaps does not
+ // return heaps in index order. That's fine, because we don't rely on
+ // mHeaps being in index order.
+ mHeaps.add(new AhatHeap(heap.getName(), snapshot.getHeapIndex(heap)));
+ TObjectProcedure<Instance> doCreate = new TObjectProcedure<Instance>() {
+ @Override
+ public boolean execute(Instance inst) {
+ long id = inst.getId();
+ if (inst instanceof ClassInstance) {
+ mInstances.add(new AhatClassInstance(id));
+ } else if (inst instanceof ArrayInstance) {
+ mInstances.add(new AhatArrayInstance(id));
+ } else if (inst instanceof ClassObj) {
+ AhatClassObj classObj = new AhatClassObj(id);
+ mInstances.add(classObj);
+ mClasses.put(((ClassObj)inst).getClassName(), classObj);
+ }
+ return true;
+ }
+ };
+ for (Instance instance : heap.getClasses()) {
+ doCreate.execute(instance);
+ }
+ heap.forEachInstance(doCreate);
+ }
+
+ // Sort the instances by id so we can use binary search to lookup
+ // instances by id.
+ mInstances.sort(new Comparator<AhatInstance>() {
+ @Override
+ public int compare(AhatInstance a, AhatInstance b) {
+ return Long.compare(a.getId(), b.getId());
+ }
+ });
+
+ // Initialize ahat snapshot and instances based on the perflib snapshot
+ // and instances.
+ for (AhatInstance ahat : mInstances) {
+ Instance inst = snapshot.findInstance(ahat.getId());
+ ahat.initialize(this, inst);
+
+ if (inst.getImmediateDominator() == Snapshot.SENTINEL_ROOT) {
+ mRooted.add(ahat);
+ }
+
+ if (inst.isReachable()) {
+ ahat.getHeap().addToSize(ahat.getSize());
+ }
+
+ // Update sites.
+ StackFrame[] frames = null;
+ StackTrace stack = inst.getStack();
+ if (stack != null) {
+ frames = stack.getFrames();
+ }
+ Site site = mRootSite.add(frames, frames == null ? 0 : frames.length, ahat);
+ ahat.setSite(site);
+ }
+
+ // Record the roots and their types.
+ for (RootObj root : snapshot.getGCRoots()) {
+ Instance inst = root.getReferredInstance();
+ if (inst != null) {
+ findInstance(inst.getId()).addRootType(root.getRootType().toString());
+ }
+ }
+ snapshot.dispose();
+ }
+
+ /**
+ * Returns the instance with given id in this snapshot.
+ * Returns null if no instance with the given id is found.
+ */
+ public AhatInstance findInstance(long id) {
+ // Binary search over the sorted instances.
+ int start = 0;
+ int end = mInstances.size();
+ while (start < end) {
+ int mid = start + ((end - start) / 2);
+ AhatInstance midInst = mInstances.get(mid);
+ long midId = midInst.getId();
+ if (id == midId) {
+ return midInst;
+ } else if (id < midId) {
+ end = mid;
+ } else {
+ start = mid + 1;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the AhatClassObj with given id in this snapshot.
+ * Returns null if no class object with the given id is found.
+ */
+ public AhatClassObj findClassObj(long id) {
+ AhatInstance inst = findInstance(id);
+ return inst == null ? null : inst.asClassObj();
+ }
+
+ /**
+ * Returns the class object for the class with given name.
+ * Returns null if there is no class object for the given name.
+ * Note: This method is exposed for testing purposes.
+ */
+ public AhatClassObj findClass(String name) {
+ return mClasses.get(name);
+ }
+
+ /**
+ * Returns the heap with the given name, if any.
+ * Returns null if no heap with the given name could be found.
+ */
+ public AhatHeap getHeap(String name) {
+ // We expect a small number of heaps (maybe 3 or 4 total), so a linear
+ // search should be acceptable here performance wise.
+ for (AhatHeap heap : getHeaps()) {
+ if (heap.getName().equals(name)) {
+ return heap;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a list of heaps in the snapshot in canonical order.
+ * Modifications to the returned list are visible to this AhatSnapshot,
+ * which is used by diff to insert place holder heaps.
+ */
+ public List<AhatHeap> getHeaps() {
+ return mHeaps;
+ }
+
+ /**
+ * Returns a collection of instances whose immediate dominator is the
+ * SENTINEL_ROOT.
+ */
+ public List<AhatInstance> getRooted() {
+ return mRooted;
+ }
+
+ /**
+ * Returns the root site for this snapshot.
+ */
+ public Site getRootSite() {
+ return mRootSite;
+ }
+
+ // Get the site associated with the given id and depth.
+ // Returns the root site if no such site found.
+ public Site getSite(int id, int depth) {
+ AhatInstance obj = findInstance(id);
+ if (obj == null) {
+ return mRootSite;
+ }
+
+ Site site = obj.getSite();
+ for (int i = 0; i < depth && site.getParent() != null; i++) {
+ site = site.getParent();
+ }
+ return site;
+ }
+
+ // Return the Value for the given perflib value object.
+ Value getValue(Object value) {
+ if (value instanceof Instance) {
+ value = findInstance(((Instance)value).getId());
+ }
+ return value == null ? null : new Value(value);
+ }
+
+ public void setBaseline(AhatSnapshot baseline) {
+ mBaseline = baseline;
+ }
+
+ /**
+ * Returns true if this snapshot has been diffed against another, different
+ * snapshot.
+ */
+ public boolean isDiffed() {
+ return mBaseline != this;
+ }
+
+ @Override public AhatSnapshot getBaseline() {
+ return mBaseline;
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return false;
+ }
+}
diff --git a/tools/ahat/src/heapdump/Diff.java b/tools/ahat/src/heapdump/Diff.java
new file mode 100644
index 0000000..943e6e6
--- /dev/null
+++ b/tools/ahat/src/heapdump/Diff.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class Diff {
+ /**
+ * Perform a diff between two heap lists.
+ *
+ * Heaps are diffed based on heap name. PlaceHolder heaps will be added to
+ * the given lists as necessary so that every heap in A has a corresponding
+ * heap in B and vice-versa.
+ */
+ private static void heaps(List<AhatHeap> a, List<AhatHeap> b) {
+ int asize = a.size();
+ int bsize = b.size();
+ for (int i = 0; i < bsize; i++) {
+ // Set the B heap's baseline as null to mark that we have not yet
+ // matched it with an A heap.
+ b.get(i).setBaseline(null);
+ }
+
+ for (int i = 0; i < asize; i++) {
+ AhatHeap aheap = a.get(i);
+ aheap.setBaseline(null);
+ for (int j = 0; j < bsize; j++) {
+ AhatHeap bheap = b.get(j);
+ if (bheap.getBaseline() == null && aheap.getName().equals(bheap.getName())) {
+ // We found a match between aheap and bheap.
+ aheap.setBaseline(bheap);
+ bheap.setBaseline(aheap);
+ break;
+ }
+ }
+
+ if (aheap.getBaseline() == null) {
+ // We did not find any match for aheap in snapshot B.
+ // Create a placeholder heap in snapshot B to use as the baseline.
+ b.add(AhatHeap.newPlaceHolderHeap(aheap.getName(), aheap));
+ }
+ }
+
+ // Make placeholder heaps in snapshot A for any unmatched heaps in
+ // snapshot B.
+ for (int i = 0; i < bsize; i++) {
+ AhatHeap bheap = b.get(i);
+ if (bheap.getBaseline() == null) {
+ a.add(AhatHeap.newPlaceHolderHeap(bheap.getName(), bheap));
+ }
+ }
+ }
+
+ /**
+ * Key represents an equivalence class of AhatInstances that are allowed to
+ * be considered for correspondence between two different snapshots.
+ */
+ private static class Key {
+ // Corresponding objects must belong to classes of the same name.
+ private final String mClass;
+
+ // Corresponding objects must belong to heaps of the same name.
+ private final String mHeapName;
+
+ // Corresponding string objects must have the same value.
+ // mStringValue is set to the empty string for non-string objects.
+ private final String mStringValue;
+
+ // Corresponding class objects must have the same class name.
+ // mClassName is set to the empty string for non-class objects.
+ private final String mClassName;
+
+ // Corresponding array objects must have the same length.
+ // mArrayLength is set to 0 for non-array objects.
+ private final int mArrayLength;
+
+
+ private Key(AhatInstance inst) {
+ mClass = inst.getClassName();
+ mHeapName = inst.getHeap().getName();
+ mClassName = inst.isClassObj() ? inst.asClassObj().getName() : "";
+ String string = inst.asString();
+ mStringValue = string == null ? "" : string;
+ AhatArrayInstance array = inst.asArrayInstance();
+ mArrayLength = array == null ? 0 : array.getLength();
+ }
+
+ /**
+ * Return the key for the given instance.
+ */
+ public static Key keyFor(AhatInstance inst) {
+ return new Key(inst);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Key)) {
+ return false;
+ }
+ Key o = (Key)other;
+ return mClass.equals(o.mClass)
+ && mHeapName.equals(o.mHeapName)
+ && mStringValue.equals(o.mStringValue)
+ && mClassName.equals(o.mClassName)
+ && mArrayLength == o.mArrayLength;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mClass, mHeapName, mStringValue, mClassName, mArrayLength);
+ }
+ }
+
+ private static class InstanceListPair {
+ public final List<AhatInstance> a;
+ public final List<AhatInstance> b;
+
+ public InstanceListPair() {
+ this.a = new ArrayList<AhatInstance>();
+ this.b = new ArrayList<AhatInstance>();
+ }
+
+ public InstanceListPair(List<AhatInstance> a, List<AhatInstance> b) {
+ this.a = a;
+ this.b = b;
+ }
+ }
+
+ /**
+ * Recursively create place holder instances for the given instance and
+ * every instance dominated by that instance.
+ * Returns the place holder instance created for the given instance.
+ * Adds all allocated placeholders to the given placeholders list.
+ */
+ private static AhatInstance createPlaceHolders(AhatInstance inst,
+ List<AhatInstance> placeholders) {
+ // Don't actually use recursion, because we could easily smash the stack.
+ // Instead we iterate.
+ AhatInstance result = inst.newPlaceHolderInstance();
+ placeholders.add(result);
+ Deque<AhatInstance> deque = new ArrayDeque<AhatInstance>();
+ deque.push(inst);
+ while (!deque.isEmpty()) {
+ inst = deque.pop();
+
+ for (AhatInstance child : inst.getDominated()) {
+ placeholders.add(child.newPlaceHolderInstance());
+ deque.push(child);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Recursively diff two dominator trees of instances.
+ * PlaceHolder objects are appended to the lists as needed to ensure every
+ * object has a corresponding baseline in the other list. All PlaceHolder
+ * objects are also appended to the given placeholders list, so their Site
+ * info can be updated later on.
+ */
+ private static void instances(List<AhatInstance> a, List<AhatInstance> b,
+ List<AhatInstance> placeholders) {
+ // Don't actually use recursion, because we could easily smash the stack.
+ // Instead we iterate.
+ Deque<InstanceListPair> deque = new ArrayDeque<InstanceListPair>();
+ deque.push(new InstanceListPair(a, b));
+ while (!deque.isEmpty()) {
+ InstanceListPair p = deque.pop();
+
+ // Group instances of the same equivalence class together.
+ Map<Key, InstanceListPair> byKey = new HashMap<Key, InstanceListPair>();
+ for (AhatInstance inst : p.a) {
+ Key key = Key.keyFor(inst);
+ InstanceListPair pair = byKey.get(key);
+ if (pair == null) {
+ pair = new InstanceListPair();
+ byKey.put(key, pair);
+ }
+ pair.a.add(inst);
+ }
+ for (AhatInstance inst : p.b) {
+ Key key = Key.keyFor(inst);
+ InstanceListPair pair = byKey.get(key);
+ if (pair == null) {
+ pair = new InstanceListPair();
+ byKey.put(key, pair);
+ }
+ pair.b.add(inst);
+ }
+
+ // diff objects from the same key class.
+ for (InstanceListPair pair : byKey.values()) {
+ // Sort by retained size and assume the elements at the top of the lists
+ // correspond to each other in that order. This could probably be
+ // improved if desired, but it gives good enough results for now.
+ Collections.sort(pair.a, Sort.INSTANCE_BY_TOTAL_RETAINED_SIZE);
+ Collections.sort(pair.b, Sort.INSTANCE_BY_TOTAL_RETAINED_SIZE);
+
+ int common = Math.min(pair.a.size(), pair.b.size());
+ for (int i = 0; i < common; i++) {
+ AhatInstance ainst = pair.a.get(i);
+ AhatInstance binst = pair.b.get(i);
+ ainst.setBaseline(binst);
+ binst.setBaseline(ainst);
+ deque.push(new InstanceListPair(ainst.getDominated(), binst.getDominated()));
+ }
+
+ // Add placeholder objects for anything leftover.
+ for (int i = common; i < pair.a.size(); i++) {
+ p.b.add(createPlaceHolders(pair.a.get(i), placeholders));
+ }
+
+ for (int i = common; i < pair.b.size(); i++) {
+ p.a.add(createPlaceHolders(pair.b.get(i), placeholders));
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the baseline for root and all its descendants to baseline.
+ */
+ private static void setSitesBaseline(Site root, Site baseline) {
+ root.setBaseline(baseline);
+ for (Site child : root.getChildren()) {
+ setSitesBaseline(child, baseline);
+ }
+ }
+
+ /**
+ * Recursively diff the two sites, setting them and their descendants as
+ * baselines for each other as appropriate.
+ *
+ * This requires that instances have already been diffed. In particular, we
+ * require all AhatClassObjs in one snapshot have corresponding (possibly
+ * place-holder) AhatClassObjs in the other snapshot.
+ */
+ private static void sites(Site a, Site b) {
+ // Set the sites as baselines of each other.
+ a.setBaseline(b);
+ b.setBaseline(a);
+
+ // Set the site's ObjectsInfos as baselines of each other. This implicitly
+ // adds new empty ObjectsInfo as needed.
+ for (Site.ObjectsInfo ainfo : a.getObjectsInfos()) {
+ AhatClassObj baseClassObj = null;
+ if (ainfo.classObj != null) {
+ baseClassObj = (AhatClassObj) ainfo.classObj.getBaseline();
+ }
+ ainfo.setBaseline(b.getObjectsInfo(ainfo.heap.getBaseline(), baseClassObj));
+ }
+ for (Site.ObjectsInfo binfo : b.getObjectsInfos()) {
+ AhatClassObj baseClassObj = null;
+ if (binfo.classObj != null) {
+ baseClassObj = (AhatClassObj) binfo.classObj.getBaseline();
+ }
+ binfo.setBaseline(a.getObjectsInfo(binfo.heap.getBaseline(), baseClassObj));
+ }
+
+ // Set B children's baselines as null to mark that we have not yet matched
+ // them with A children.
+ for (Site bchild : b.getChildren()) {
+ bchild.setBaseline(null);
+ }
+
+ for (Site achild : a.getChildren()) {
+ achild.setBaseline(null);
+ for (Site bchild : b.getChildren()) {
+ if (achild.getLineNumber() == bchild.getLineNumber()
+ && achild.getMethodName().equals(bchild.getMethodName())
+ && achild.getSignature().equals(bchild.getSignature())
+ && achild.getFilename().equals(bchild.getFilename())) {
+ // We found a match between achild and bchild.
+ sites(achild, bchild);
+ break;
+ }
+ }
+
+ if (achild.getBaseline() == null) {
+ // We did not find any match for achild in site B.
+ // Use B for the baseline of achild and its descendants.
+ setSitesBaseline(achild, b);
+ }
+ }
+
+ for (Site bchild : b.getChildren()) {
+ if (bchild.getBaseline() == null) {
+ setSitesBaseline(bchild, a);
+ }
+ }
+ }
+
+ /**
+ * Perform a diff of the two snapshots, setting each as the baseline for the
+ * other.
+ */
+ public static void snapshots(AhatSnapshot a, AhatSnapshot b) {
+ a.setBaseline(b);
+ b.setBaseline(a);
+
+ // Diff the heaps of each snapshot.
+ heaps(a.getHeaps(), b.getHeaps());
+
+ // Diff the instances of each snapshot.
+ List<AhatInstance> placeholders = new ArrayList<AhatInstance>();
+ instances(a.getRooted(), b.getRooted(), placeholders);
+
+ // Diff the sites of each snapshot.
+ // This requires the instances have already been diffed.
+ sites(a.getRootSite(), b.getRootSite());
+
+ // Add placeholders to their corresponding sites.
+ // This requires the sites have already been diffed.
+ for (AhatInstance placeholder : placeholders) {
+ placeholder.getBaseline().getSite().getBaseline().addPlaceHolderInstance(placeholder);
+ }
+ }
+
+ /**
+ * Diff two lists of field values.
+ * PlaceHolder objects are added to the given lists as needed to ensure
+ * every FieldValue in A ends up with a corresponding FieldValue in B.
+ */
+ public static void fields(List<FieldValue> a, List<FieldValue> b) {
+ // Fields with the same name and type are considered matching fields.
+ // For simplicity, we assume the matching fields are in the same order in
+ // both A and B, though some fields may be added or removed in either
+ // list. If our assumption is wrong, in the worst case the quality of the
+ // field diff is poor.
+
+ for (int i = 0; i < a.size(); i++) {
+ FieldValue afield = a.get(i);
+ afield.setBaseline(null);
+
+ // Find the matching field in B, if any.
+ for (int j = i; j < b.size(); j++) {
+ FieldValue bfield = b.get(j);
+ if (afield.getName().equals(bfield.getName())
+ && afield.getType().equals(bfield.getType())) {
+ // We found the matching field in B.
+ // Assume fields i, ..., j-1 in B have no match in A.
+ for ( ; i < j; i++) {
+ a.add(i, FieldValue.newPlaceHolderFieldValue(b.get(i)));
+ }
+
+ afield.setBaseline(bfield);
+ bfield.setBaseline(afield);
+ break;
+ }
+ }
+
+ if (afield.getBaseline() == null) {
+ b.add(i, FieldValue.newPlaceHolderFieldValue(afield));
+ }
+ }
+
+ // All remaining fields in B are unmatched by any in A.
+ for (int i = a.size(); i < b.size(); i++) {
+ a.add(i, FieldValue.newPlaceHolderFieldValue(b.get(i)));
+ }
+ }
+}
diff --git a/tools/ahat/src/heapdump/Diffable.java b/tools/ahat/src/heapdump/Diffable.java
new file mode 100644
index 0000000..53442c8
--- /dev/null
+++ b/tools/ahat/src/heapdump/Diffable.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat.heapdump;
+
+/**
+ * An interface for objects that have corresponding objects in a baseline heap
+ * dump.
+ */
+public interface Diffable<T> {
+ /**
+ * Return the baseline object that corresponds to this one.
+ */
+ T getBaseline();
+
+ /**
+ * Returns true if this is a placeholder object.
+ * A placeholder object is used to indicate there is some object in the
+ * baseline heap dump that is not in this heap dump. In that case, we create
+ * a dummy place holder object in this heap dump as an indicator of the
+ * object removed from the baseline heap dump.
+ */
+ boolean isPlaceHolder();
+}
+
diff --git a/tools/ahat/src/heapdump/FieldValue.java b/tools/ahat/src/heapdump/FieldValue.java
new file mode 100644
index 0000000..3f65cd3
--- /dev/null
+++ b/tools/ahat/src/heapdump/FieldValue.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat.heapdump;
+
+public class FieldValue implements Diffable<FieldValue> {
+ private final String mName;
+ private final String mType;
+ private final Value mValue;
+ private FieldValue mBaseline;
+ private final boolean mIsPlaceHolder;
+
+ public FieldValue(String name, String type, Value value) {
+ mName = name;
+ mType = type;
+ mValue = value;
+ mBaseline = this;
+ mIsPlaceHolder = false;
+ }
+
+ /**
+ * Construct a place holder FieldValue
+ */
+ private FieldValue(FieldValue baseline) {
+ mName = baseline.mName;
+ mType = baseline.mType;
+ mValue = Value.getBaseline(baseline.mValue);
+ mBaseline = baseline;
+ mIsPlaceHolder = true;
+ }
+
+ static FieldValue newPlaceHolderFieldValue(FieldValue baseline) {
+ FieldValue field = new FieldValue(baseline);
+ baseline.setBaseline(field);
+ return field;
+ }
+
+ /**
+ * Returns the name of the field.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns a description of the type of the field.
+ */
+ public String getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the value of this field.
+ */
+ public Value getValue() {
+ return mValue;
+ }
+
+ public void setBaseline(FieldValue baseline) {
+ mBaseline = baseline;
+ }
+
+ @Override public FieldValue getBaseline() {
+ return mBaseline;
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return mIsPlaceHolder;
+ }
+}
diff --git a/tools/ahat/src/heapdump/PathElement.java b/tools/ahat/src/heapdump/PathElement.java
new file mode 100644
index 0000000..196a246
--- /dev/null
+++ b/tools/ahat/src/heapdump/PathElement.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat.heapdump;
+
+public class PathElement implements Diffable<PathElement> {
+ public final AhatInstance instance;
+ public final String field;
+ public boolean isDominator;
+
+ public PathElement(AhatInstance instance, String field) {
+ this.instance = instance;
+ this.field = field;
+ this.isDominator = false;
+ }
+
+ @Override public PathElement getBaseline() {
+ return this;
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return false;
+ }
+}
diff --git a/tools/ahat/src/heapdump/Site.java b/tools/ahat/src/heapdump/Site.java
new file mode 100644
index 0000000..738eaf0
--- /dev/null
+++ b/tools/ahat/src/heapdump/Site.java
@@ -0,0 +1,284 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.StackFrame;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Site implements Diffable<Site> {
+ // The site that this site was directly called from.
+ // mParent is null for the root site.
+ private Site mParent;
+
+ private String mMethodName;
+ private String mSignature;
+ private String mFilename;
+ private int mLineNumber;
+
+ // To identify this site, we pick a stack trace that includes the site.
+ // mId is the id of an object allocated at that stack trace, and mDepth
+ // is the number of calls between this site and the innermost site of
+ // allocation of the object with mId.
+ // For the root site, mId is 0 and mDepth is 0.
+ private long mId;
+ private int mDepth;
+
+ // The total size of objects allocated in this site (including child sites),
+ // organized by heap index. Heap indices outside the range of mSizesByHeap
+ // implicitly have size 0.
+ private long[] mSizesByHeap;
+
+ // List of child sites.
+ private List<Site> mChildren;
+
+ // List of all objects allocated in this site (including child sites).
+ private List<AhatInstance> mObjects;
+ private List<ObjectsInfo> mObjectsInfos;
+ private Map<AhatHeap, Map<AhatClassObj, ObjectsInfo>> mObjectsInfoMap;
+
+ private Site mBaseline;
+
+ public static class ObjectsInfo implements Diffable<ObjectsInfo> {
+ public AhatHeap heap;
+ public AhatClassObj classObj; // May be null.
+ public long numInstances;
+ public long numBytes;
+ private ObjectsInfo baseline;
+
+ public ObjectsInfo(AhatHeap heap, AhatClassObj classObj, long numInstances, long numBytes) {
+ this.heap = heap;
+ this.classObj = classObj;
+ this.numInstances = numInstances;
+ this.numBytes = numBytes;
+ this.baseline = this;
+ }
+
+ /**
+ * Returns the name of the class this ObjectsInfo is associated with.
+ */
+ public String getClassName() {
+ return classObj == null ? "???" : classObj.getName();
+ }
+
+ public void setBaseline(ObjectsInfo baseline) {
+ this.baseline = baseline;
+ }
+
+ @Override public ObjectsInfo getBaseline() {
+ return baseline;
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return false;
+ }
+ }
+
+ /**
+ * Construct a root site.
+ */
+ public Site(String name) {
+ this(null, name, "", "", 0, 0, 0);
+ }
+
+ public Site(Site parent, String method, String signature, String file,
+ int line, long id, int depth) {
+ mParent = parent;
+ mMethodName = method;
+ mSignature = signature;
+ mFilename = file;
+ mLineNumber = line;
+ mId = id;
+ mDepth = depth;
+ mSizesByHeap = new long[1];
+ mChildren = new ArrayList<Site>();
+ mObjects = new ArrayList<AhatInstance>();
+ mObjectsInfos = new ArrayList<ObjectsInfo>();
+ mObjectsInfoMap = new HashMap<AhatHeap, Map<AhatClassObj, ObjectsInfo>>();
+ mBaseline = this;
+ }
+
+ /**
+ * Add an instance to this site.
+ * Returns the site at which the instance was allocated.
+ * @param frames - The list of frames in the stack trace, starting with the inner-most frame.
+ * @param depth - The number of frames remaining before the inner-most frame is reached.
+ */
+ Site add(StackFrame[] frames, int depth, AhatInstance inst) {
+ return add(this, frames, depth, inst);
+ }
+
+ private static Site add(Site site, StackFrame[] frames, int depth, AhatInstance inst) {
+ while (true) {
+ site.mObjects.add(inst);
+
+ ObjectsInfo info = site.getObjectsInfo(inst.getHeap(), inst.getClassObj());
+ if (inst.isReachable()) {
+ AhatHeap heap = inst.getHeap();
+ if (heap.getIndex() >= site.mSizesByHeap.length) {
+ long[] newSizes = new long[heap.getIndex() + 1];
+ for (int i = 0; i < site.mSizesByHeap.length; i++) {
+ newSizes[i] = site.mSizesByHeap[i];
+ }
+ site.mSizesByHeap = newSizes;
+ }
+ site.mSizesByHeap[heap.getIndex()] += inst.getSize();
+
+ info.numInstances++;
+ info.numBytes += inst.getSize();
+ }
+
+ if (depth > 0) {
+ StackFrame next = frames[depth - 1];
+ Site child = null;
+ for (int i = 0; i < site.mChildren.size(); i++) {
+ Site curr = site.mChildren.get(i);
+ if (curr.mLineNumber == next.getLineNumber()
+ && curr.mMethodName.equals(next.getMethodName())
+ && curr.mSignature.equals(next.getSignature())
+ && curr.mFilename.equals(next.getFilename())) {
+ child = curr;
+ break;
+ }
+ }
+ if (child == null) {
+ child = new Site(site, next.getMethodName(), next.getSignature(),
+ next.getFilename(), next.getLineNumber(), inst.getId(), depth - 1);
+ site.mChildren.add(child);
+ }
+ depth = depth - 1;
+ site = child;
+ } else {
+ return site;
+ }
+ }
+ }
+
+ // Get the size of a site for a specific heap.
+ public long getSize(AhatHeap heap) {
+ int index = heap.getIndex();
+ return index >= 0 && index < mSizesByHeap.length ? mSizesByHeap[index] : 0;
+ }
+
+ /**
+ * Get the list of objects allocated under this site. Includes objects
+ * allocated in children sites.
+ */
+ public Collection<AhatInstance> getObjects() {
+ return mObjects;
+ }
+
+ /**
+ * Returns the ObjectsInfo at this site for the given heap and class
+ * objects. Creates a new empty ObjectsInfo if none existed before.
+ */
+ ObjectsInfo getObjectsInfo(AhatHeap heap, AhatClassObj classObj) {
+ Map<AhatClassObj, ObjectsInfo> classToObjectsInfo = mObjectsInfoMap.get(heap);
+ if (classToObjectsInfo == null) {
+ classToObjectsInfo = new HashMap<AhatClassObj, ObjectsInfo>();
+ mObjectsInfoMap.put(heap, classToObjectsInfo);
+ }
+
+ ObjectsInfo info = classToObjectsInfo.get(classObj);
+ if (info == null) {
+ info = new ObjectsInfo(heap, classObj, 0, 0);
+ mObjectsInfos.add(info);
+ classToObjectsInfo.put(classObj, info);
+ }
+ return info;
+ }
+
+ public List<ObjectsInfo> getObjectsInfos() {
+ return mObjectsInfos;
+ }
+
+ // Get the combined size of the site for all heaps.
+ public long getTotalSize() {
+ long total = 0;
+ for (int i = 0; i < mSizesByHeap.length; i++) {
+ total += mSizesByHeap[i];
+ }
+ return total;
+ }
+
+ /**
+ * Return the site this site was called from.
+ * Returns null for the root site.
+ */
+ public Site getParent() {
+ return mParent;
+ }
+
+ public String getMethodName() {
+ return mMethodName;
+ }
+
+ public String getSignature() {
+ return mSignature;
+ }
+
+ public String getFilename() {
+ return mFilename;
+ }
+
+ public int getLineNumber() {
+ return mLineNumber;
+ }
+
+ /**
+ * Returns the id of some object allocated in this site.
+ */
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the number of frames between this site and the site where the
+ * object with id getId() was allocated.
+ */
+ public int getDepth() {
+ return mDepth;
+ }
+
+ public List<Site> getChildren() {
+ return mChildren;
+ }
+
+ void setBaseline(Site baseline) {
+ mBaseline = baseline;
+ }
+
+ @Override public Site getBaseline() {
+ return mBaseline;
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return false;
+ }
+
+ /**
+ * Adds a place holder instance to this site and all parent sites.
+ */
+ void addPlaceHolderInstance(AhatInstance placeholder) {
+ for (Site site = this; site != null; site = site.mParent) {
+ site.mObjects.add(placeholder);
+ }
+ }
+}
diff --git a/tools/ahat/src/heapdump/Sort.java b/tools/ahat/src/heapdump/Sort.java
new file mode 100644
index 0000000..93d147a
--- /dev/null
+++ b/tools/ahat/src/heapdump/Sort.java
@@ -0,0 +1,193 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Provides Comparators and helper functions for sorting Instances, Sites, and
+ * other things.
+ *
+ * Note: The Comparators defined here impose orderings that are inconsistent
+ * with equals. They should not be used for element lookup or search. They
+ * should only be used for showing elements to the user in different orders.
+ */
+public class Sort {
+ /**
+ * Compare instances by their total retained size.
+ * Different instances with the same total retained size are considered
+ * equal for the purposes of comparison.
+ * This sorts instances from larger retained size to smaller retained size.
+ */
+ public static final Comparator<AhatInstance> INSTANCE_BY_TOTAL_RETAINED_SIZE
+ = new Comparator<AhatInstance>() {
+ @Override
+ public int compare(AhatInstance a, AhatInstance b) {
+ return Long.compare(b.getTotalRetainedSize(), a.getTotalRetainedSize());
+ }
+ };
+
+ /**
+ * Compare instances by their retained size for a given heap index.
+ * Different instances with the same total retained size are considered
+ * equal for the purposes of comparison.
+ * This sorts instances from larger retained size to smaller retained size.
+ */
+ public static class InstanceByHeapRetainedSize implements Comparator<AhatInstance> {
+ private AhatHeap mHeap;
+
+ public InstanceByHeapRetainedSize(AhatHeap heap) {
+ mHeap = heap;
+ }
+
+ @Override
+ public int compare(AhatInstance a, AhatInstance b) {
+ return Long.compare(b.getRetainedSize(mHeap), a.getRetainedSize(mHeap));
+ }
+ }
+
+ /**
+ * Compare objects based on a list of comparators, giving priority to the
+ * earlier comparators in the list.
+ */
+ public static class WithPriority<T> implements Comparator<T> {
+ private List<Comparator<T>> mComparators;
+
+ public WithPriority(Comparator<T>... comparators) {
+ mComparators = Arrays.asList(comparators);
+ }
+
+ public WithPriority(List<Comparator<T>> comparators) {
+ mComparators = comparators;
+ }
+
+ @Override
+ public int compare(T a, T b) {
+ int res = 0;
+ Iterator<Comparator<T>> iter = mComparators.iterator();
+ while (res == 0 && iter.hasNext()) {
+ res = iter.next().compare(a, b);
+ }
+ return res;
+ }
+ }
+
+ public static Comparator<AhatInstance> defaultInstanceCompare(AhatSnapshot snapshot) {
+ List<Comparator<AhatInstance>> comparators = new ArrayList<Comparator<AhatInstance>>();
+
+ // Priority goes to the app heap, if we can find one.
+ AhatHeap appHeap = snapshot.getHeap("app");
+ if (appHeap != null) {
+ comparators.add(new InstanceByHeapRetainedSize(appHeap));
+ }
+
+ // Next is by total retained size.
+ comparators.add(INSTANCE_BY_TOTAL_RETAINED_SIZE);
+ return new WithPriority<AhatInstance>(comparators);
+ }
+
+ /**
+ * Compare Sites by the size of objects allocated on a given heap.
+ * Different object infos with the same size on the given heap are
+ * considered equal for the purposes of comparison.
+ * This sorts sites from larger size to smaller size.
+ */
+ public static class SiteByHeapSize implements Comparator<Site> {
+ AhatHeap mHeap;
+
+ public SiteByHeapSize(AhatHeap heap) {
+ mHeap = heap;
+ }
+
+ @Override
+ public int compare(Site a, Site b) {
+ return Long.compare(b.getSize(mHeap), a.getSize(mHeap));
+ }
+ }
+
+ /**
+ * Compare Sites by the total size of objects allocated.
+ * This sorts sites from larger size to smaller size.
+ */
+ public static final Comparator<Site> SITE_BY_TOTAL_SIZE = new Comparator<Site>() {
+ @Override
+ public int compare(Site a, Site b) {
+ return Long.compare(b.getTotalSize(), a.getTotalSize());
+ }
+ };
+
+ public static Comparator<Site> defaultSiteCompare(AhatSnapshot snapshot) {
+ List<Comparator<Site>> comparators = new ArrayList<Comparator<Site>>();
+
+ // Priority goes to the app heap, if we can find one.
+ AhatHeap appHeap = snapshot.getHeap("app");
+ if (appHeap != null) {
+ comparators.add(new SiteByHeapSize(appHeap));
+ }
+
+ // Next is by total size.
+ comparators.add(SITE_BY_TOTAL_SIZE);
+ return new WithPriority<Site>(comparators);
+ }
+
+ /**
+ * Compare Site.ObjectsInfo by their size.
+ * Different object infos with the same total retained size are considered
+ * equal for the purposes of comparison.
+ * This sorts object infos from larger retained size to smaller size.
+ */
+ public static final Comparator<Site.ObjectsInfo> OBJECTS_INFO_BY_SIZE
+ = new Comparator<Site.ObjectsInfo>() {
+ @Override
+ public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
+ return Long.compare(b.numBytes, a.numBytes);
+ }
+ };
+
+ /**
+ * Compare Site.ObjectsInfo by heap name.
+ * Different object infos with the same heap name are considered equal for
+ * the purposes of comparison.
+ */
+ public static final Comparator<Site.ObjectsInfo> OBJECTS_INFO_BY_HEAP_NAME
+ = new Comparator<Site.ObjectsInfo>() {
+ @Override
+ public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
+ return a.heap.getName().compareTo(b.heap.getName());
+ }
+ };
+
+ /**
+ * Compare Site.ObjectsInfo by class name.
+ * Different object infos with the same class name are considered equal for
+ * the purposes of comparison.
+ */
+ public static final Comparator<Site.ObjectsInfo> OBJECTS_INFO_BY_CLASS_NAME
+ = new Comparator<Site.ObjectsInfo>() {
+ @Override
+ public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
+ String aName = a.getClassName();
+ String bName = b.getClassName();
+ return aName.compareTo(bName);
+ }
+ };
+}
+
diff --git a/tools/ahat/src/heapdump/Value.java b/tools/ahat/src/heapdump/Value.java
new file mode 100644
index 0000000..6b2d38f
--- /dev/null
+++ b/tools/ahat/src/heapdump/Value.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat.heapdump;
+
+/**
+ * Value represents a field value in a heap dump. The field value is either a
+ * subclass of AhatInstance or a primitive Java type.
+ */
+public class Value {
+ private Object mObject;
+
+ /**
+ * Constructs a value from a generic Java Object.
+ * The Object must either be a boxed Java primitive type or a subclass of
+ * AhatInstance. The object must not be null.
+ */
+ Value(Object object) {
+ // TODO: Check that the Object is either an AhatSnapshot or boxed Java
+ // primitive type?
+ assert object != null;
+ mObject = object;
+ }
+
+ /**
+ * Returns true if the Value is an AhatInstance, as opposed to a Java
+ * primitive value.
+ */
+ public boolean isAhatInstance() {
+ return mObject instanceof AhatInstance;
+ }
+
+ /**
+ * Return the Value as an AhatInstance if it is one.
+ * Returns null if the Value represents a Java primitive value.
+ */
+ public AhatInstance asAhatInstance() {
+ if (isAhatInstance()) {
+ return (AhatInstance)mObject;
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the Value is an Integer.
+ */
+ public boolean isInteger() {
+ return mObject instanceof Integer;
+ }
+
+ /**
+ * Return the Value as an Integer if it is one.
+ * Returns null if the Value does not represent an Integer.
+ */
+ public Integer asInteger() {
+ if (isInteger()) {
+ return (Integer)mObject;
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the Value is an Long.
+ */
+ public boolean isLong() {
+ return mObject instanceof Long;
+ }
+
+ /**
+ * Return the Value as an Long if it is one.
+ * Returns null if the Value does not represent an Long.
+ */
+ public Long asLong() {
+ if (isLong()) {
+ return (Long)mObject;
+ }
+ return null;
+ }
+
+ /**
+ * Return the Value as a Byte if it is one.
+ * Returns null if the Value does not represent a Byte.
+ */
+ public Byte asByte() {
+ if (mObject instanceof Byte) {
+ return (Byte)mObject;
+ }
+ return null;
+ }
+
+ /**
+ * Return the Value as a Char if it is one.
+ * Returns null if the Value does not represent a Char.
+ */
+ public Character asChar() {
+ if (mObject instanceof Character) {
+ return (Character)mObject;
+ }
+ return null;
+ }
+
+ public String toString() {
+ return mObject.toString();
+ }
+
+ public static Value getBaseline(Value value) {
+ if (value == null || !value.isAhatInstance()) {
+ return value;
+ }
+ return new Value(value.asAhatInstance().getBaseline());
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof Value) {
+ Value value = (Value)other;
+ return mObject.equals(value.mObject);
+ }
+ return false;
+ }
+}
diff --git a/tools/ahat/src/help.html b/tools/ahat/src/help.html
deleted file mode 100644
index ff04ad2..0000000
--- a/tools/ahat/src/help.html
+++ /dev/null
@@ -1,80 +0,0 @@
-<!--
-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.
--->
-
-<h1>Help</h1>
-<h2>Information shown by ahat:</h2>
-<ul>
- <li><a href="/">The total bytes retained by heap.</a></li>
- <li><a href="/rooted">A list of rooted objects and their retained sizes for each heap.</a></li>
- <li>Information about each allocated object:
- <ul>
- <li>The allocation site (stack trace) of the object (if available).</li>
- <li>The dominator path from a root to the object.</li>
- <li>The class, (shallow) size, retained size, and heap of the object.</li>
- <li>The bitmap image for the object if the object represents a bitmap.</li>
- <li>The instance fields or array elements of the object.</li>
- <li>The super class, class loader, and static fields of class objects.</li>
- <li>Other objects with references to the object.</li>
- <li>Other objects immediately dominated by the object.</li>
- </ul>
- </li>
- <li>A list of objects, optionally filtered by class, allocation site, and/or
- heap.</li>
- <li><a href="site">Information about each allocation site:</a>
- <ul>
- <li>The stack trace for the allocation site.</li>
- <li>The number of bytes allocated at the allocation site.</li>
- <li>Child sites called from the allocation site.</li>
- <li>The size and count of objects allocated at the site, organized by
- heap and object type.</li>
- </ul>
- </li>
-</ul>
-
-<h2>Tips:</h2>
-<h3>Heaps</h3>
-<p>
-Android heap dumps contain information for multiple heaps. The <b>app</b> heap
-is the memory used by your application. The <b>zygote</b> and <b>image</b>
-heaps are used by the system. You should ignore everything in the zygote and
-image heap and look only at the app heap. This is because changes in your
-application will not effect the zygote or image heaps, and because the zygote
-and image heaps are shared, they don't contribute significantly to your
-applications PSS.
-</p>
-
-<h3>Bitmaps</h3>
-<p>
-Bitmaps store their data using byte[] arrays. Whenever you see a large
-byte[], check if it is a bitmap by looking to see if there is a single
-android.graphics.Bitmap object referring to it. The byte[] will be marked as a
-root, but it is really being retained by the android.graphics.Bitmap object.
-</p>
-
-<h3>DexCaches</h3>
-<p>
-For each DexFile you load, there will be a corresponding DexCache whose size
-is proportional to the number of strings, fields, methods, and classes in your
-dex file. The DexCache entries may or may not be visible depending on the
-version of the Android platform the heap dump is from.
-</p>
-
-<h3>FinalizerReferences</h3>
-<p>
-A FinalizerReference is allocated for every object on the heap that has a
-non-trivial finalizer. These are stored in a linked list reachable from the
-FinalizerReference class object.
-</p>
diff --git a/tools/ahat/src/manifest.txt b/tools/ahat/src/manifest.txt
index 1993910..20245f3 100644
--- a/tools/ahat/src/manifest.txt
+++ b/tools/ahat/src/manifest.txt
@@ -1,4 +1,4 @@
Name: ahat/
Implementation-Title: ahat
-Implementation-Version: 0.8
+Implementation-Version: 1.1
Main-Class: com.android.ahat.Main
diff --git a/tools/ahat/src/style.css b/tools/ahat/src/style.css
index ca074a5..47fae1d 100644
--- a/tools/ahat/src/style.css
+++ b/tools/ahat/src/style.css
@@ -18,6 +18,14 @@
background-color: #eeffff;
}
+span.added {
+ color: #770000;
+}
+
+span.removed {
+ color: #007700;
+}
+
/*
* Most of the columns show numbers of bytes. Numbers should be right aligned.
*/
diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java
index 587d9de..7a05b1c 100644
--- a/tools/ahat/test-dump/Main.java
+++ b/tools/ahat/test-dump/Main.java
@@ -18,8 +18,9 @@
import java.io.IOException;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
-import libcore.util.NativeAllocationRegistry;
+import org.apache.harmony.dalvik.ddmc.DdmVmInternal;
/**
* Program used to create a heap dump for test purposes.
@@ -39,13 +40,32 @@
}
}
+ public static class AddedObject {
+ }
+
+ public static class RemovedObject {
+ }
+
+ public static class UnchangedObject {
+ }
+
+ public static class ModifiedObject {
+ public int value;
+ public String modifiedRefField;
+ public String unmodifiedRefField;
+ }
+
+ public static class StackSmasher {
+ public StackSmasher child;
+ }
+
// We will take a heap dump that includes a single instance of this
// DumpedStuff class. Objects stored as fields in this class can be easily
// found in the hprof dump by searching for the instance of the DumpedStuff
// class and reading the desired field.
public static class DumpedStuff {
public String basicString = "hello, world";
- public String nonAscii = "Sigma (\u01a9) is not ASCII";
+ public String nonAscii = "Sigma (Æ©) is not ASCII";
public String embeddedZero = "embedded\0..."; // Non-ASCII for string compression purposes.
public char[] charArray = "char thing".toCharArray();
public String nullString = null;
@@ -53,23 +73,53 @@
public ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
public PhantomReference aPhantomReference = new PhantomReference(anObject, referenceQueue);
public WeakReference aWeakReference = new WeakReference(anObject, referenceQueue);
+ public WeakReference aNullReferentReference = new WeakReference(null, referenceQueue);
+ public SoftReference aSoftReference = new SoftReference(new Object());
public byte[] bigArray;
public ObjectTree[] gcPathArray = new ObjectTree[]{null, null,
new ObjectTree(
new ObjectTree(null, new ObjectTree(null, null)),
new ObjectTree(null, null)),
null};
+ public Object[] basicStringRef;
+ public AddedObject addedObject;
+ public UnchangedObject unchangedObject = new UnchangedObject();
+ public RemovedObject removedObject;
+ public ModifiedObject modifiedObject;
+ public StackSmasher stackSmasher;
+ public StackSmasher stackSmasherAdded;
+ public static String modifiedStaticField;
+ public int[] modifiedArray;
- DumpedStuff() {
- int N = 1000000;
+ DumpedStuff(boolean baseline) {
+ int N = baseline ? 400000 : 1000000;
bigArray = new byte[N];
for (int i = 0; i < N; i++) {
bigArray[i] = (byte)((i*i) & 0xFF);
}
- NativeAllocationRegistry registry = new NativeAllocationRegistry(
- Main.class.getClassLoader(), 0x12345, 42);
- registry.registerNativeAllocation(anObject, 0xABCDABCD);
+ addedObject = baseline ? null : new AddedObject();
+ removedObject = baseline ? new RemovedObject() : null;
+ modifiedObject = new ModifiedObject();
+ modifiedObject.value = baseline ? 5 : 8;
+ modifiedObject.modifiedRefField = baseline ? "A1" : "A2";
+ modifiedObject.unmodifiedRefField = "B";
+ modifiedStaticField = baseline ? "C1" : "C2";
+ modifiedArray = baseline ? new int[]{0,1,2,3} : new int[]{3,1,2,0};
+
+ // Deep matching dominator trees shouldn't smash the stack when we try
+ // to diff them. Make some deep dominator trees to help test it.
+ for (int i = 0; i < 10000; i++) {
+ StackSmasher smasher = new StackSmasher();
+ smasher.child = stackSmasher;
+ stackSmasher = smasher;
+
+ if (!baseline) {
+ smasher = new StackSmasher();
+ smasher.child = stackSmasherAdded;
+ stackSmasherAdded = smasher;
+ }
+ }
gcPathArray[2].right.left = gcPathArray[2].left.right;
}
@@ -82,8 +132,21 @@
}
String file = args[0];
+ // If a --base argument is provided, it means we should generate a
+ // baseline hprof file suitable for using in testing diff.
+ boolean baseline = args.length > 1 && args[1].equals("--base");
+
+ // Enable allocation tracking so we get stack traces in the heap dump.
+ DdmVmInternal.enableRecentAllocations(true);
+
// Allocate the instance of DumpedStuff.
- stuff = new DumpedStuff();
+ stuff = new DumpedStuff(baseline);
+
+ // Create a bunch of unreachable objects pointing to basicString for the
+ // reverseReferencesAreNotUnreachable test
+ for (int i = 0; i < 100; i++) {
+ stuff.basicStringRef = new Object[]{stuff.basicString};
+ }
// Take a heap dump that will include that instance of DumpedStuff.
System.err.println("Dumping hprof data to " + file);
diff --git a/tools/ahat/test/DiffTest.java b/tools/ahat/test/DiffTest.java
new file mode 100644
index 0000000..52b6b7b
--- /dev/null
+++ b/tools/ahat/test/DiffTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Diff;
+import com.android.ahat.heapdump.FieldValue;
+import com.android.tools.perflib.heap.hprof.HprofClassDump;
+import com.android.tools.perflib.heap.hprof.HprofConstant;
+import com.android.tools.perflib.heap.hprof.HprofDumpRecord;
+import com.android.tools.perflib.heap.hprof.HprofHeapDump;
+import com.android.tools.perflib.heap.hprof.HprofInstanceDump;
+import com.android.tools.perflib.heap.hprof.HprofInstanceField;
+import com.android.tools.perflib.heap.hprof.HprofLoadClass;
+import com.android.tools.perflib.heap.hprof.HprofPrimitiveArrayDump;
+import com.android.tools.perflib.heap.hprof.HprofRecord;
+import com.android.tools.perflib.heap.hprof.HprofRootDebugger;
+import com.android.tools.perflib.heap.hprof.HprofStaticField;
+import com.android.tools.perflib.heap.hprof.HprofStringBuilder;
+import com.android.tools.perflib.heap.hprof.HprofType;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class DiffTest {
+ @Test
+ public void diffMatchedHeap() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatHeap a = dump.getAhatSnapshot().getHeap("app");
+ assertNotNull(a);
+ AhatHeap b = dump.getBaselineAhatSnapshot().getHeap("app");
+ assertNotNull(b);
+ assertEquals(a.getBaseline(), b);
+ assertEquals(b.getBaseline(), a);
+ }
+
+ @Test
+ public void diffUnchanged() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatInstance a = dump.getDumpedAhatInstance("unchangedObject");
+ assertNotNull(a);
+
+ AhatInstance b = dump.getBaselineDumpedAhatInstance("unchangedObject");
+ assertNotNull(b);
+ assertEquals(a, b.getBaseline());
+ assertEquals(b, a.getBaseline());
+ assertEquals(a.getSite(), b.getSite().getBaseline());
+ assertEquals(b.getSite(), a.getSite().getBaseline());
+ }
+
+ @Test
+ public void diffAdded() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatInstance a = dump.getDumpedAhatInstance("addedObject");
+ assertNotNull(a);
+ assertNull(dump.getBaselineDumpedAhatInstance("addedObject"));
+ assertTrue(a.getBaseline().isPlaceHolder());
+ }
+
+ @Test
+ public void diffRemoved() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ assertNull(dump.getDumpedAhatInstance("removedObject"));
+ AhatInstance b = dump.getBaselineDumpedAhatInstance("removedObject");
+ assertNotNull(b);
+ assertTrue(b.getBaseline().isPlaceHolder());
+ }
+
+ @Test
+ public void nullClassObj() throws IOException {
+ // Set up a heap dump that has a null classObj.
+ // The heap dump is derived from the InstanceTest.asStringEmbedded test.
+ HprofStringBuilder strings = new HprofStringBuilder(0);
+ List<HprofRecord> records = new ArrayList<HprofRecord>();
+ List<HprofDumpRecord> dump = new ArrayList<HprofDumpRecord>();
+
+ final int stringClassObjectId = 1;
+ records.add(new HprofLoadClass(0, 0, stringClassObjectId, 0, strings.get("java.lang.String")));
+ dump.add(new HprofClassDump(stringClassObjectId, 0, 0, 0, 0, 0, 0, 0, 0,
+ new HprofConstant[0], new HprofStaticField[0],
+ new HprofInstanceField[]{
+ new HprofInstanceField(strings.get("count"), HprofType.TYPE_INT),
+ new HprofInstanceField(strings.get("hashCode"), HprofType.TYPE_INT),
+ new HprofInstanceField(strings.get("offset"), HprofType.TYPE_INT),
+ new HprofInstanceField(strings.get("value"), HprofType.TYPE_OBJECT)}));
+
+ dump.add(new HprofPrimitiveArrayDump(0x41, 0, HprofType.TYPE_CHAR,
+ new long[]{'n', 'o', 't', ' ', 'h', 'e', 'l', 'l', 'o', 'o', 'p'}));
+
+ ByteArrayDataOutput values = ByteStreams.newDataOutput();
+ values.writeInt(5); // count
+ values.writeInt(0); // hashCode
+ values.writeInt(4); // offset
+ values.writeInt(0x41); // value
+ dump.add(new HprofInstanceDump(0x42, 0, stringClassObjectId, values.toByteArray()));
+ dump.add(new HprofRootDebugger(stringClassObjectId));
+ dump.add(new HprofRootDebugger(0x42));
+
+ records.add(new HprofHeapDump(0, dump.toArray(new HprofDumpRecord[0])));
+ AhatSnapshot snapshot = SnapshotBuilder.makeSnapshot(strings, records);
+
+ // Diffing should not crash.
+ Diff.snapshots(snapshot, snapshot);
+ }
+
+ @Test
+ public void diffFields() {
+ List<FieldValue> a = new ArrayList<FieldValue>();
+ a.add(new FieldValue("n0", "t0", null));
+ a.add(new FieldValue("n2", "t2", null));
+ a.add(new FieldValue("n3", "t3", null));
+ a.add(new FieldValue("n4", "t4", null));
+ a.add(new FieldValue("n5", "t5", null));
+ a.add(new FieldValue("n6", "t6", null));
+
+ List<FieldValue> b = new ArrayList<FieldValue>();
+ b.add(new FieldValue("n0", "t0", null));
+ b.add(new FieldValue("n1", "t1", null));
+ b.add(new FieldValue("n2", "t2", null));
+ b.add(new FieldValue("n3", "t3", null));
+ b.add(new FieldValue("n5", "t5", null));
+ b.add(new FieldValue("n6", "t6", null));
+ b.add(new FieldValue("n7", "t7", null));
+
+ Diff.fields(a, b);
+ assertEquals(8, a.size());
+ assertEquals(8, b.size());
+ for (int i = 0; i < 8; i++) {
+ assertEquals(a.get(i), b.get(i).getBaseline());
+ assertEquals(b.get(i), a.get(i).getBaseline());
+ }
+ assertTrue(a.get(1).isPlaceHolder());
+ assertTrue(a.get(7).isPlaceHolder());
+ assertTrue(b.get(4).isPlaceHolder());
+ }
+}
diff --git a/tools/ahat/test/InstanceTest.java b/tools/ahat/test/InstanceTest.java
new file mode 100644
index 0000000..3a50150
--- /dev/null
+++ b/tools/ahat/test/InstanceTest.java
@@ -0,0 +1,413 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatClassObj;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.PathElement;
+import com.android.ahat.heapdump.Value;
+import com.android.tools.perflib.heap.hprof.HprofClassDump;
+import com.android.tools.perflib.heap.hprof.HprofConstant;
+import com.android.tools.perflib.heap.hprof.HprofDumpRecord;
+import com.android.tools.perflib.heap.hprof.HprofHeapDump;
+import com.android.tools.perflib.heap.hprof.HprofInstanceDump;
+import com.android.tools.perflib.heap.hprof.HprofInstanceField;
+import com.android.tools.perflib.heap.hprof.HprofLoadClass;
+import com.android.tools.perflib.heap.hprof.HprofPrimitiveArrayDump;
+import com.android.tools.perflib.heap.hprof.HprofRecord;
+import com.android.tools.perflib.heap.hprof.HprofRootDebugger;
+import com.android.tools.perflib.heap.hprof.HprofStaticField;
+import com.android.tools.perflib.heap.hprof.HprofStringBuilder;
+import com.android.tools.perflib.heap.hprof.HprofType;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class InstanceTest {
+ @Test
+ public void asStringBasic() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("basicString");
+ assertEquals("hello, world", str.asString());
+ }
+
+ @Test
+ public void asStringNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+ assertEquals("Sigma (Æ©) is not ASCII", str.asString());
+ }
+
+ @Test
+ public void asStringEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+ assertEquals("embedded\0...", str.asString());
+ }
+
+ @Test
+ public void asStringCharArray() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("charArray");
+ assertEquals("char thing", str.asString());
+ }
+
+ @Test
+ public void asStringTruncated() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("basicString");
+ assertEquals("hello", str.asString(5));
+ }
+
+ @Test
+ public void asStringTruncatedNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+ assertEquals("Sigma (Æ©)", str.asString(9));
+ }
+
+ @Test
+ public void asStringTruncatedEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+ assertEquals("embed", str.asString(5));
+ }
+
+ @Test
+ public void asStringCharArrayTruncated() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("charArray");
+ assertEquals("char ", str.asString(5));
+ }
+
+ @Test
+ public void asStringExactMax() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("basicString");
+ assertEquals("hello, world", str.asString(12));
+ }
+
+ @Test
+ public void asStringExactMaxNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+ assertEquals("Sigma (Æ©) is not ASCII", str.asString(22));
+ }
+
+ @Test
+ public void asStringExactMaxEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+ assertEquals("embedded\0...", str.asString(12));
+ }
+
+ @Test
+ public void asStringCharArrayExactMax() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("charArray");
+ assertEquals("char thing", str.asString(10));
+ }
+
+ @Test
+ public void asStringNotTruncated() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("basicString");
+ assertEquals("hello, world", str.asString(50));
+ }
+
+ @Test
+ public void asStringNotTruncatedNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+ assertEquals("Sigma (Æ©) is not ASCII", str.asString(50));
+ }
+
+ @Test
+ public void asStringNotTruncatedEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+ assertEquals("embedded\0...", str.asString(50));
+ }
+
+ @Test
+ public void asStringCharArrayNotTruncated() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("charArray");
+ assertEquals("char thing", str.asString(50));
+ }
+
+ @Test
+ public void asStringNegativeMax() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("basicString");
+ assertEquals("hello, world", str.asString(-3));
+ }
+
+ @Test
+ public void asStringNegativeMaxNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+ assertEquals("Sigma (Æ©) is not ASCII", str.asString(-3));
+ }
+
+ @Test
+ public void asStringNegativeMaxEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+ assertEquals("embedded\0...", str.asString(-3));
+ }
+
+ @Test
+ public void asStringCharArrayNegativeMax() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("charArray");
+ assertEquals("char thing", str.asString(-3));
+ }
+
+ @Test
+ public void asStringNull() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("nullString");
+ assertNull(obj);
+ }
+
+ @Test
+ public void asStringNotString() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("anObject");
+ assertNotNull(obj);
+ assertNull(obj.asString());
+ }
+
+ @Test
+ public void basicReference() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatInstance pref = dump.getDumpedAhatInstance("aPhantomReference");
+ AhatInstance wref = dump.getDumpedAhatInstance("aWeakReference");
+ AhatInstance nref = dump.getDumpedAhatInstance("aNullReferentReference");
+ AhatInstance referent = dump.getDumpedAhatInstance("anObject");
+ assertNotNull(pref);
+ assertNotNull(wref);
+ assertNotNull(nref);
+ assertNotNull(referent);
+ assertEquals(referent, pref.getReferent());
+ assertEquals(referent, wref.getReferent());
+ assertNull(nref.getReferent());
+ assertNull(referent.getReferent());
+ }
+
+ @Test
+ public void unreachableReferent() throws IOException {
+ // The test dump program should never be under enough GC pressure for the
+ // soft reference to be cleared. Ensure that ahat will show the soft
+ // reference as having a non-null referent.
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance ref = dump.getDumpedAhatInstance("aSoftReference");
+ assertNotNull(ref.getReferent());
+ }
+
+ @Test
+ public void gcRootPath() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatClassObj main = dump.getAhatSnapshot().findClass("Main");
+ AhatInstance gcPathArray = dump.getDumpedAhatInstance("gcPathArray");
+ Value value = gcPathArray.asArrayInstance().getValue(2);
+ AhatInstance base = value.asAhatInstance();
+ AhatInstance left = base.getRefField("left");
+ AhatInstance right = base.getRefField("right");
+ AhatInstance target = left.getRefField("right");
+
+ List<PathElement> path = target.getPathFromGcRoot();
+ assertEquals(6, path.size());
+
+ assertEquals(main, path.get(0).instance);
+ assertEquals(".stuff", path.get(0).field);
+ assertTrue(path.get(0).isDominator);
+
+ assertEquals(".gcPathArray", path.get(1).field);
+ assertTrue(path.get(1).isDominator);
+
+ assertEquals(gcPathArray, path.get(2).instance);
+ assertEquals("[2]", path.get(2).field);
+ assertTrue(path.get(2).isDominator);
+
+ assertEquals(base, path.get(3).instance);
+ assertTrue(path.get(3).isDominator);
+
+ // There are two possible paths. Either it can go through the 'left' node,
+ // or the 'right' node.
+ if (path.get(3).field.equals(".left")) {
+ assertEquals(".left", path.get(3).field);
+
+ assertEquals(left, path.get(4).instance);
+ assertEquals(".right", path.get(4).field);
+ assertFalse(path.get(4).isDominator);
+
+ } else {
+ assertEquals(".right", path.get(3).field);
+
+ assertEquals(right, path.get(4).instance);
+ assertEquals(".left", path.get(4).field);
+ assertFalse(path.get(4).isDominator);
+ }
+
+ assertEquals(target, path.get(5).instance);
+ assertEquals("", path.get(5).field);
+ assertTrue(path.get(5).isDominator);
+ }
+
+ @Test
+ public void retainedSize() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ // anObject should not be an immediate dominator of any other object. This
+ // means its retained size should be equal to its size for the heap it was
+ // allocated on, and should be 0 for all other heaps.
+ AhatInstance anObject = dump.getDumpedAhatInstance("anObject");
+ AhatSnapshot snapshot = dump.getAhatSnapshot();
+ long size = anObject.getSize();
+ assertEquals(size, anObject.getTotalRetainedSize());
+ assertEquals(size, anObject.getRetainedSize(anObject.getHeap()));
+ for (AhatHeap heap : snapshot.getHeaps()) {
+ if (!heap.equals(anObject.getHeap())) {
+ assertEquals(String.format("For heap '%s'", heap.getName()),
+ 0, anObject.getRetainedSize(heap));
+ }
+ }
+ }
+
+ @Test
+ public void objectNotABitmap() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("anObject");
+ assertNull(obj.asBitmap());
+ }
+
+ @Test
+ public void arrayNotABitmap() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray");
+ assertNull(obj.asBitmap());
+ }
+
+ @Test
+ public void classObjNotABitmap() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getAhatSnapshot().findClass("Main");
+ assertNull(obj.asBitmap());
+ }
+
+ @Test
+ public void classInstanceToString() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("aPhantomReference");
+ long id = obj.getId();
+ assertEquals(String.format("java.lang.ref.PhantomReference@%08x", id), obj.toString());
+ }
+
+ @Test
+ public void classObjToString() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getAhatSnapshot().findClass("Main");
+ assertEquals("Main", obj.toString());
+ }
+
+ @Test
+ public void arrayInstanceToString() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray");
+ long id = obj.getId();
+
+ // There's a bug in perfib's proguard deobfuscation for arrays.
+ // To work around that bug for the time being, only test the suffix of
+ // the toString result. Ideally we test for string equality against
+ // "Main$ObjectTree[4]@%08x", id.
+ assertTrue(obj.toString().endsWith(String.format("[4]@%08x", id)));
+ }
+
+ @Test
+ public void primArrayInstanceToString() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("bigArray");
+ long id = obj.getId();
+ assertEquals(String.format("byte[1000000]@%08x", id), obj.toString());
+ }
+
+ @Test
+ public void isNotRoot() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("anObject");
+ assertFalse(obj.isRoot());
+ assertNull(obj.getRootTypes());
+ }
+
+ @Test
+ public void asStringEmbedded() throws IOException {
+ // Set up a heap dump with an instance of java.lang.String of
+ // "hello" with instance id 0x42 that is backed by a char array that is
+ // bigger. This is how ART used to represent strings, and we should still
+ // support it in case the heap dump is from a previous platform version.
+ HprofStringBuilder strings = new HprofStringBuilder(0);
+ List<HprofRecord> records = new ArrayList<HprofRecord>();
+ List<HprofDumpRecord> dump = new ArrayList<HprofDumpRecord>();
+
+ final int stringClassObjectId = 1;
+ records.add(new HprofLoadClass(0, 0, stringClassObjectId, 0, strings.get("java.lang.String")));
+ dump.add(new HprofClassDump(stringClassObjectId, 0, 0, 0, 0, 0, 0, 0, 0,
+ new HprofConstant[0], new HprofStaticField[0],
+ new HprofInstanceField[]{
+ new HprofInstanceField(strings.get("count"), HprofType.TYPE_INT),
+ new HprofInstanceField(strings.get("hashCode"), HprofType.TYPE_INT),
+ new HprofInstanceField(strings.get("offset"), HprofType.TYPE_INT),
+ new HprofInstanceField(strings.get("value"), HprofType.TYPE_OBJECT)}));
+
+ dump.add(new HprofPrimitiveArrayDump(0x41, 0, HprofType.TYPE_CHAR,
+ new long[]{'n', 'o', 't', ' ', 'h', 'e', 'l', 'l', 'o', 'o', 'p'}));
+
+ ByteArrayDataOutput values = ByteStreams.newDataOutput();
+ values.writeInt(5); // count
+ values.writeInt(0); // hashCode
+ values.writeInt(4); // offset
+ values.writeInt(0x41); // value
+ dump.add(new HprofInstanceDump(0x42, 0, stringClassObjectId, values.toByteArray()));
+ dump.add(new HprofRootDebugger(stringClassObjectId));
+ dump.add(new HprofRootDebugger(0x42));
+
+ records.add(new HprofHeapDump(0, dump.toArray(new HprofDumpRecord[0])));
+ AhatSnapshot snapshot = SnapshotBuilder.makeSnapshot(strings, records);
+ AhatInstance chars = snapshot.findInstance(0x41);
+ assertNotNull(chars);
+ assertEquals("not helloop", chars.asString());
+
+ AhatInstance stringInstance = snapshot.findInstance(0x42);
+ assertNotNull(stringInstance);
+ assertEquals("hello", stringInstance.asString());
+ }
+}
diff --git a/tools/ahat/test/InstanceUtilsTest.java b/tools/ahat/test/InstanceUtilsTest.java
deleted file mode 100644
index fe2706d..0000000
--- a/tools/ahat/test/InstanceUtilsTest.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.ArrayInstance;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Instance;
-import java.io.IOException;
-import java.util.List;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import org.junit.Test;
-
-public class InstanceUtilsTest {
- @Test
- public void asStringBasic() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("basicString");
- assertEquals("hello, world", InstanceUtils.asString(str));
- }
-
- @Test
- public void asStringNonAscii() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("nonAscii");
- assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str));
- }
-
- @Test
- public void asStringEmbeddedZero() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("embeddedZero");
- assertEquals("embedded\0...", InstanceUtils.asString(str));
- }
-
- @Test
- public void asStringCharArray() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("charArray");
- assertEquals("char thing", InstanceUtils.asString(str));
- }
-
- @Test
- public void asStringTruncated() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("basicString");
- assertEquals("hello", InstanceUtils.asString(str, 5));
- }
-
- @Test
- public void asStringTruncatedNonAscii() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("nonAscii");
- assertEquals("Sigma (\u01a9)", InstanceUtils.asString(str, 9));
- }
-
- @Test
- public void asStringTruncatedEmbeddedZero() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("embeddedZero");
- assertEquals("embed", InstanceUtils.asString(str, 5));
- }
-
- @Test
- public void asStringCharArrayTruncated() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("charArray");
- assertEquals("char ", InstanceUtils.asString(str, 5));
- }
-
- @Test
- public void asStringExactMax() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("basicString");
- assertEquals("hello, world", InstanceUtils.asString(str, 12));
- }
-
- @Test
- public void asStringExactMaxNonAscii() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("nonAscii");
- assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, 22));
- }
-
- @Test
- public void asStringExactMaxEmbeddedZero() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("embeddedZero");
- assertEquals("embedded\0...", InstanceUtils.asString(str, 12));
- }
-
- @Test
- public void asStringCharArrayExactMax() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("charArray");
- assertEquals("char thing", InstanceUtils.asString(str, 10));
- }
-
- @Test
- public void asStringNotTruncated() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("basicString");
- assertEquals("hello, world", InstanceUtils.asString(str, 50));
- }
-
- @Test
- public void asStringNotTruncatedNonAscii() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("nonAscii");
- assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, 50));
- }
-
- @Test
- public void asStringNotTruncatedEmbeddedZero() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("embeddedZero");
- assertEquals("embedded\0...", InstanceUtils.asString(str, 50));
- }
-
- @Test
- public void asStringCharArrayNotTruncated() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("charArray");
- assertEquals("char thing", InstanceUtils.asString(str, 50));
- }
-
- @Test
- public void asStringNegativeMax() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("basicString");
- assertEquals("hello, world", InstanceUtils.asString(str, -3));
- }
-
- @Test
- public void asStringNegativeMaxNonAscii() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("nonAscii");
- assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, -3));
- }
-
- @Test
- public void asStringNegativeMaxEmbeddedZero() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("embeddedZero");
- assertEquals("embedded\0...", InstanceUtils.asString(str, -3));
- }
-
- @Test
- public void asStringCharArrayNegativeMax() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("charArray");
- assertEquals("char thing", InstanceUtils.asString(str, -3));
- }
-
- @Test
- public void asStringNull() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance obj = (Instance)dump.getDumpedThing("nullString");
- assertNull(InstanceUtils.asString(obj));
- }
-
- @Test
- public void asStringNotString() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance obj = (Instance)dump.getDumpedThing("anObject");
- assertNotNull(obj);
- assertNull(InstanceUtils.asString(obj));
- }
-
- @Test
- public void basicReference() throws IOException {
- TestDump dump = TestDump.getTestDump();
-
- Instance pref = (Instance)dump.getDumpedThing("aPhantomReference");
- Instance wref = (Instance)dump.getDumpedThing("aWeakReference");
- Instance referent = (Instance)dump.getDumpedThing("anObject");
- assertNotNull(pref);
- assertNotNull(wref);
- assertNotNull(referent);
- assertEquals(referent, InstanceUtils.getReferent(pref));
- assertEquals(referent, InstanceUtils.getReferent(wref));
- assertNull(InstanceUtils.getReferent(referent));
- }
-
- @Test
- public void gcRootPath() throws IOException {
- TestDump dump = TestDump.getTestDump();
-
- ClassObj main = dump.getAhatSnapshot().findClass("Main");
- ArrayInstance gcPathArray = (ArrayInstance)dump.getDumpedThing("gcPathArray");
- Object[] values = gcPathArray.getValues();
- Instance base = (Instance)values[2];
- Instance left = InstanceUtils.getRefField(base, "left");
- Instance right = InstanceUtils.getRefField(base, "right");
- Instance target = InstanceUtils.getRefField(left, "right");
-
- List<InstanceUtils.PathElement> path = InstanceUtils.getPathFromGcRoot(target);
- assertEquals(6, path.size());
-
- assertEquals(main, path.get(0).instance);
- assertEquals(".stuff", path.get(0).field);
- assertTrue(path.get(0).isDominator);
-
- assertEquals(".gcPathArray", path.get(1).field);
- assertTrue(path.get(1).isDominator);
-
- assertEquals(gcPathArray, path.get(2).instance);
- assertEquals("[2]", path.get(2).field);
- assertTrue(path.get(2).isDominator);
-
- assertEquals(base, path.get(3).instance);
- assertTrue(path.get(3).isDominator);
-
- // There are two possible paths. Either it can go through the 'left' node,
- // or the 'right' node.
- if (path.get(3).field.equals(".left")) {
- assertEquals(".left", path.get(3).field);
-
- assertEquals(left, path.get(4).instance);
- assertEquals(".right", path.get(4).field);
- assertFalse(path.get(4).isDominator);
-
- } else {
- assertEquals(".right", path.get(3).field);
-
- assertEquals(right, path.get(4).instance);
- assertEquals(".left", path.get(4).field);
- assertFalse(path.get(4).isDominator);
- }
-
- assertEquals(target, path.get(5).instance);
- assertEquals("", path.get(5).field);
- assertTrue(path.get(5).isDominator);
- }
-}
diff --git a/tools/ahat/test/NativeAllocationTest.java b/tools/ahat/test/NativeAllocationTest.java
deleted file mode 100644
index 7ad4c1d..0000000
--- a/tools/ahat/test/NativeAllocationTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.Instance;
-import java.io.IOException;
-import static org.junit.Assert.fail;
-import static org.junit.Assert.assertEquals;
-import org.junit.Test;
-
-public class NativeAllocationTest {
-
- @Test
- public void nativeAllocation() throws IOException {
- TestDump dump = TestDump.getTestDump();
-
- AhatSnapshot snapshot = dump.getAhatSnapshot();
- Instance referent = (Instance)dump.getDumpedThing("anObject");
- for (InstanceUtils.NativeAllocation alloc : snapshot.getNativeAllocations()) {
- if (alloc.referent == referent) {
- assertEquals(42 , alloc.size);
- assertEquals(referent.getHeap(), alloc.heap);
- assertEquals(0xABCDABCD , alloc.pointer);
- return;
- }
- }
- fail("No native allocation found with anObject as the referent");
- }
-}
-
diff --git a/tools/ahat/test/ObjectHandlerTest.java b/tools/ahat/test/ObjectHandlerTest.java
new file mode 100644
index 0000000..cd0ba23
--- /dev/null
+++ b/tools/ahat/test/ObjectHandlerTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import java.io.IOException;
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+
+public class ObjectHandlerTest {
+ @Test
+ public void noCrashClassInstance() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatInstance object = dump.getDumpedAhatInstance("aPhantomReference");
+ assertNotNull(object);
+
+ AhatHandler handler = new ObjectHandler(dump.getAhatSnapshot());
+ TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
+ }
+
+ @Test
+ public void noCrashClassObj() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatSnapshot snapshot = dump.getAhatSnapshot();
+ AhatHandler handler = new ObjectHandler(snapshot);
+
+ AhatInstance object = snapshot.findClass("Main");
+ assertNotNull(object);
+
+ TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
+ }
+
+ @Test
+ public void noCrashSystemClassObj() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatSnapshot snapshot = dump.getAhatSnapshot();
+ AhatHandler handler = new ObjectHandler(snapshot);
+
+ AhatInstance object = snapshot.findClass("java.lang.String");
+ assertNotNull(object);
+
+ TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
+ }
+
+ @Test
+ public void noCrashArrayInstance() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatInstance object = dump.getDumpedAhatInstance("gcPathArray");
+ assertNotNull(object);
+
+ AhatHandler handler = new ObjectHandler(dump.getAhatSnapshot());
+ TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
+ }
+}
diff --git a/tools/ahat/test/OverviewHandlerTest.java b/tools/ahat/test/OverviewHandlerTest.java
new file mode 100644
index 0000000..c2f773b
--- /dev/null
+++ b/tools/ahat/test/OverviewHandlerTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatSnapshot;
+import java.io.File;
+import java.io.IOException;
+import org.junit.Test;
+
+public class OverviewHandlerTest {
+
+ @Test
+ public void noCrash() throws IOException {
+ AhatSnapshot snapshot = TestDump.getTestDump().getAhatSnapshot();
+ AhatHandler handler = new OverviewHandler(snapshot,
+ new File("my.hprof.file"),
+ new File("my.base.hprof.file"));
+ TestHandler.testNoCrash(handler, "http://localhost:7100");
+ }
+}
diff --git a/tools/ahat/test/PerformanceTest.java b/tools/ahat/test/PerformanceTest.java
index 6e46800..e13974b 100644
--- a/tools/ahat/test/PerformanceTest.java
+++ b/tools/ahat/test/PerformanceTest.java
@@ -16,13 +16,15 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
+import org.junit.Test;
+
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import org.junit.Test;
public class PerformanceTest {
private static class NullOutputStream extends OutputStream {
@@ -36,7 +38,7 @@
// for any object, including big arrays.
TestDump dump = TestDump.getTestDump();
- Instance bigArray = (Instance)dump.getDumpedThing("bigArray");
+ AhatInstance bigArray = dump.getDumpedAhatInstance("bigArray");
assertNotNull(bigArray);
AhatSnapshot snapshot = dump.getAhatSnapshot();
diff --git a/tools/ahat/test/QueryTest.java b/tools/ahat/test/QueryTest.java
index 40e3322..5bcf8ea 100644
--- a/tools/ahat/test/QueryTest.java
+++ b/tools/ahat/test/QueryTest.java
@@ -18,9 +18,10 @@
import java.net.URI;
import java.net.URISyntaxException;
-import static org.junit.Assert.assertEquals;
import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
public class QueryTest {
@Test
public void simple() throws URISyntaxException {
diff --git a/tools/ahat/test/RootedHandlerTest.java b/tools/ahat/test/RootedHandlerTest.java
new file mode 100644
index 0000000..f325b8e
--- /dev/null
+++ b/tools/ahat/test/RootedHandlerTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatSnapshot;
+import java.io.IOException;
+import org.junit.Test;
+
+public class RootedHandlerTest {
+ @Test
+ public void noCrash() throws IOException {
+ AhatSnapshot snapshot = TestDump.getTestDump().getAhatSnapshot();
+ AhatHandler handler = new RootedHandler(snapshot);
+ TestHandler.testNoCrash(handler, "http://localhost:7100/rooted");
+ }
+}
diff --git a/tools/ahat/test/SiteHandlerTest.java b/tools/ahat/test/SiteHandlerTest.java
new file mode 100644
index 0000000..37596be
--- /dev/null
+++ b/tools/ahat/test/SiteHandlerTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatSnapshot;
+import java.io.IOException;
+import org.junit.Test;
+
+public class SiteHandlerTest {
+ @Test
+ public void noCrash() throws IOException {
+ AhatSnapshot snapshot = TestDump.getTestDump().getAhatSnapshot();
+ AhatHandler handler = new SiteHandler(snapshot);
+ TestHandler.testNoCrash(handler, "http://localhost:7100/sites");
+ }
+}
diff --git a/tools/ahat/test/SnapshotBuilder.java b/tools/ahat/test/SnapshotBuilder.java
new file mode 100644
index 0000000..0eea635
--- /dev/null
+++ b/tools/ahat/test/SnapshotBuilder.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.tools.perflib.heap.ProguardMap;
+import com.android.tools.perflib.heap.hprof.Hprof;
+import com.android.tools.perflib.heap.hprof.HprofRecord;
+import com.android.tools.perflib.heap.hprof.HprofStringBuilder;
+import com.android.tools.perflib.heap.io.InMemoryBuffer;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Class with utilities to help constructing snapshots for tests.
+ */
+public class SnapshotBuilder {
+
+ // Helper function to make a snapshot with id size 4 given an
+ // HprofStringBuilder and list of HprofRecords
+ public static AhatSnapshot makeSnapshot(HprofStringBuilder strings, List<HprofRecord> records)
+ throws IOException {
+ // TODO: When perflib can handle the case where strings are referred to
+ // before they are defined, just add the string records to the records
+ // list.
+ List<HprofRecord> actualRecords = new ArrayList<HprofRecord>();
+ actualRecords.addAll(strings.getStringRecords());
+ actualRecords.addAll(records);
+
+ Hprof hprof = new Hprof("JAVA PROFILE 1.0.3", 4, new Date(), actualRecords);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ hprof.write(os);
+ InMemoryBuffer buffer = new InMemoryBuffer(os.toByteArray());
+ return AhatSnapshot.fromDataBuffer(buffer, new ProguardMap());
+ }
+}
diff --git a/tools/ahat/test/SortTest.java b/tools/ahat/test/SortTest.java
deleted file mode 100644
index 02ff7db..0000000
--- a/tools/ahat/test/SortTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Heap;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import static org.junit.Assert.assertEquals;
-import org.junit.Test;
-
-public class SortTest {
- @Test
- public void objectsInfo() {
- Heap heapA = new Heap(0xA, "A");
- Heap heapB = new Heap(0xB, "B");
- ClassObj classA = new ClassObj(0x1A, null, "classA", 0);
- ClassObj classB = new ClassObj(0x1B, null, "classB", 0);
- ClassObj classC = new ClassObj(0x1C, null, "classC", 0);
- Site.ObjectsInfo infoA = new Site.ObjectsInfo(heapA, classA, 4, 14);
- Site.ObjectsInfo infoB = new Site.ObjectsInfo(heapB, classB, 2, 15);
- Site.ObjectsInfo infoC = new Site.ObjectsInfo(heapA, classC, 3, 13);
- Site.ObjectsInfo infoD = new Site.ObjectsInfo(heapB, classA, 5, 12);
- Site.ObjectsInfo infoE = new Site.ObjectsInfo(heapA, classB, 1, 11);
- List<Site.ObjectsInfo> list = new ArrayList<Site.ObjectsInfo>();
- list.add(infoA);
- list.add(infoB);
- list.add(infoC);
- list.add(infoD);
- list.add(infoE);
-
- // Sort by size.
- Collections.sort(list, new Sort.ObjectsInfoBySize());
- assertEquals(infoB, list.get(0));
- assertEquals(infoA, list.get(1));
- assertEquals(infoC, list.get(2));
- assertEquals(infoD, list.get(3));
- assertEquals(infoE, list.get(4));
-
- // Sort by class name.
- Collections.sort(list, new Sort.ObjectsInfoByClassName());
- assertEquals(classA, list.get(0).classObj);
- assertEquals(classA, list.get(1).classObj);
- assertEquals(classB, list.get(2).classObj);
- assertEquals(classB, list.get(3).classObj);
- assertEquals(classC, list.get(4).classObj);
-
- // Sort by heap name.
- Collections.sort(list, new Sort.ObjectsInfoByHeapName());
- assertEquals(heapA, list.get(0).heap);
- assertEquals(heapA, list.get(1).heap);
- assertEquals(heapA, list.get(2).heap);
- assertEquals(heapB, list.get(3).heap);
- assertEquals(heapB, list.get(4).heap);
-
- // Sort first by class name, then by size.
- Collections.sort(list, new Sort.WithPriority<Site.ObjectsInfo>(
- new Sort.ObjectsInfoByClassName(),
- new Sort.ObjectsInfoBySize()));
- assertEquals(infoA, list.get(0));
- assertEquals(infoD, list.get(1));
- assertEquals(infoB, list.get(2));
- assertEquals(infoE, list.get(3));
- assertEquals(infoC, list.get(4));
- }
-}
diff --git a/tools/ahat/test/TestDump.java b/tools/ahat/test/TestDump.java
index ebce61c..ceb7346 100644
--- a/tools/ahat/test/TestDump.java
+++ b/tools/ahat/test/TestDump.java
@@ -16,14 +16,16 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Field;
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatClassObj;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Diff;
+import com.android.ahat.heapdump.FieldValue;
+import com.android.ahat.heapdump.Value;
import com.android.tools.perflib.heap.ProguardMap;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
-import java.util.Map;
/**
* The TestDump class is used to get an AhatSnapshot for the test-dump
@@ -37,30 +39,46 @@
// is visible to other test cases.
private static TestDump mCachedTestDump = null;
+ // If the test dump fails to load the first time, it will likely fail every
+ // other test we try. Rather than having to wait a potentially very long
+ // time for test dump loading to fail over and over again, record when it
+ // fails and don't try to load it again.
+ private static boolean mTestDumpFailed = false;
+
private AhatSnapshot mSnapshot = null;
+ private AhatSnapshot mBaseline = null;
/**
- * Load the test-dump.hprof file.
- * The location of the file is read from the system property
- * "ahat.test.dump.hprof", which is expected to be set on the command line.
- * For example:
- * java -Dahat.test.dump.hprof=test-dump.hprof -jar ahat-tests.jar
+ * Load the test-dump.hprof and test-dump-base.hprof files.
+ * The location of the files are read from the system properties
+ * "ahat.test.dump.hprof" and "ahat.test.dump.base.hprof", which is expected
+ * to be set on the command line.
+ * The location of the proguard map for both hprof files is read from the
+ * system property "ahat.test.dump.map". For example:
+ * java -Dahat.test.dump.hprof=test-dump.hprof \
+ * -Dahat.test.dump.base.hprof=test-dump-base.hprof \
+ * -Dahat.test.dump.map=proguard.map \
+ * -jar ahat-tests.jar
*
- * An IOException is thrown if there is a failure reading the hprof file or
+ * An IOException is thrown if there is a failure reading the hprof files or
* the proguard map.
*/
private TestDump() throws IOException {
- String hprof = System.getProperty("ahat.test.dump.hprof");
+ // TODO: Make use of the baseline hprof for tests.
+ String hprof = System.getProperty("ahat.test.dump.hprof");
+ String hprofBase = System.getProperty("ahat.test.dump.base.hprof");
- String mapfile = System.getProperty("ahat.test.dump.map");
- ProguardMap map = new ProguardMap();
- try {
- map.readFromFile(new File(mapfile));
- } catch (ParseException e) {
- throw new IOException("Unable to load proguard map", e);
- }
+ String mapfile = System.getProperty("ahat.test.dump.map");
+ ProguardMap map = new ProguardMap();
+ try {
+ map.readFromFile(new File(mapfile));
+ } catch (ParseException e) {
+ throw new IOException("Unable to load proguard map", e);
+ }
- mSnapshot = AhatSnapshot.fromHprof(new File(hprof), map);
+ mSnapshot = AhatSnapshot.fromHprof(new File(hprof), map);
+ mBaseline = AhatSnapshot.fromHprof(new File(hprofBase), map);
+ Diff.snapshots(mSnapshot, mBaseline);
}
/**
@@ -71,18 +89,59 @@
}
/**
- * Return the value of a field in the DumpedStuff instance in the
+ * Get the baseline AhatSnapshot for the test dump program.
+ */
+ public AhatSnapshot getBaselineAhatSnapshot() {
+ return mBaseline;
+ }
+
+ /**
+ * Returns the value of a field in the DumpedStuff instance in the
* snapshot for the test-dump program.
*/
- public Object getDumpedThing(String name) {
- ClassObj main = mSnapshot.findClass("Main");
- Instance stuff = null;
- for (Map.Entry<Field, Object> fields : main.getStaticFieldValues().entrySet()) {
- if ("stuff".equals(fields.getKey().getName())) {
- stuff = (Instance) fields.getValue();
+ public Value getDumpedValue(String name) {
+ return getDumpedValue(name, mSnapshot);
+ }
+
+ /**
+ * Returns the value of a field in the DumpedStuff instance in the
+ * baseline snapshot for the test-dump program.
+ */
+ public Value getBaselineDumpedValue(String name) {
+ return getDumpedValue(name, mBaseline);
+ }
+
+ /**
+ * Returns the value of a field in the DumpedStuff instance in the
+ * given snapshot for the test-dump program.
+ */
+ private Value getDumpedValue(String name, AhatSnapshot snapshot) {
+ AhatClassObj main = snapshot.findClass("Main");
+ AhatInstance stuff = null;
+ for (FieldValue fields : main.getStaticFieldValues()) {
+ if ("stuff".equals(fields.getName())) {
+ stuff = fields.getValue().asAhatInstance();
}
}
- return InstanceUtils.getField(stuff, name);
+ return stuff.getField(name);
+ }
+
+ /**
+ * Returns the value of a non-primitive field in the DumpedStuff instance in
+ * the snapshot for the test-dump program.
+ */
+ public AhatInstance getDumpedAhatInstance(String name) {
+ Value value = getDumpedValue(name);
+ return value == null ? null : value.asAhatInstance();
+ }
+
+ /**
+ * Returns the value of a non-primitive field in the DumpedStuff instance in
+ * the baseline snapshot for the test-dump program.
+ */
+ public AhatInstance getBaselineDumpedAhatInstance(String name) {
+ Value value = getBaselineDumpedValue(name);
+ return value == null ? null : value.asAhatInstance();
}
/**
@@ -93,8 +152,14 @@
* when possible.
*/
public static synchronized TestDump getTestDump() throws IOException {
+ if (mTestDumpFailed) {
+ throw new RuntimeException("Test dump failed before, assuming it will again");
+ }
+
if (mCachedTestDump == null) {
+ mTestDumpFailed = true;
mCachedTestDump = new TestDump();
+ mTestDumpFailed = false;
}
return mCachedTestDump;
}
diff --git a/tools/ahat/test/TestHandler.java b/tools/ahat/test/TestHandler.java
new file mode 100644
index 0000000..859e39a
--- /dev/null
+++ b/tools/ahat/test/TestHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.ahat;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * Provide common utilities for basic handler tests.
+ */
+public class TestHandler {
+ private static class NullOutputStream extends OutputStream {
+ public void write(int b) throws IOException {
+ }
+ }
+
+ /**
+ * Test that the given handler doesn't crash on the given query.
+ */
+ public static void testNoCrash(AhatHandler handler, String uri) throws IOException {
+ PrintStream ps = new PrintStream(new NullOutputStream());
+ HtmlDoc doc = new HtmlDoc(ps, DocString.text("noCrash test"), DocString.uri("style.css"));
+ Query query = new Query(DocString.uri(uri));
+ handler.handle(doc, query);
+ }
+}
diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java
index 3291470..2fd3286 100644
--- a/tools/ahat/test/Tests.java
+++ b/tools/ahat/test/Tests.java
@@ -22,11 +22,14 @@
public static void main(String[] args) {
if (args.length == 0) {
args = new String[]{
- "com.android.ahat.InstanceUtilsTest",
- "com.android.ahat.NativeAllocationTest",
+ "com.android.ahat.DiffTest",
+ "com.android.ahat.InstanceTest",
+ "com.android.ahat.ObjectHandlerTest",
+ "com.android.ahat.OverviewHandlerTest",
"com.android.ahat.PerformanceTest",
+ "com.android.ahat.RootedHandlerTest",
"com.android.ahat.QueryTest",
- "com.android.ahat.SortTest",
+ "com.android.ahat.SiteHandlerTest",
};
}
JUnitCore.main(args);
diff --git a/tools/findbuildbotwarnings.py b/tools/findbuildbotwarnings.py
new file mode 100755
index 0000000..a172dd6
--- /dev/null
+++ b/tools/findbuildbotwarnings.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+#
+# 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.
+
+"""Outputs the warnings that are common to all builders.
+
+Suppressed tests that are nonetheless passing are output as warnings
+by vogar. Any tests that generate warnings in every builder are good
+candidates for no longer being suppressed, since they're passing on
+a regular basis."""
+
+import collections
+import json
+import requests
+
+# The number of recent builds to check for each builder
+NUM_BUILDS = 5
+# The buildbot step to check for warnings
+BUILDBOT_STEP = 'test libcore'
+
+
+def main():
+ # Dict from builder+build_num combination to the list of warnings
+ # in that build
+ warnings = collections.defaultdict(list)
+ r = requests.get('https://build.chromium.org/p/client.art/json/builders')
+ if r.status_code != 200:
+ print r.text
+ return
+ builders = json.loads(r.text)
+ for builder_name in sorted(builders):
+ # Build -1 is the currently-running build (if there is one), so we
+ # start with -2, which should be the most or second-most
+ # recently-completed build.
+ for build_num in range(-2, -2 - NUM_BUILDS, -1):
+ print ('Loading data for %s, build %d...'
+ % (builder_name, build_num))
+ r = requests.get(
+ 'https://build.chromium.org/p/client.art'
+ '/json/builders/%s/builds/%d' % (
+ builder_name, build_num))
+ if r.status_code != 200:
+ print r.text
+ return
+ builder = json.loads(r.text)
+ libcore_steps = [x for x in builder['steps']
+ if x['name'] == BUILDBOT_STEP]
+ for ls in libcore_steps:
+ stdio_logs = [x for x in ls['logs'] if x[0] == 'stdio']
+ for sl in stdio_logs:
+ # The default link is HTML, so append /text to get the
+ # text version
+ r = requests.get(sl[1] + '/text')
+ if r.status_code != 200:
+ print r.text
+ return
+ stdio = r.text.splitlines()
+
+ # Walk from the back of the list to find the start of the
+ # warnings summary
+ i = -1
+ try:
+ while not stdio[i].startswith('Warnings summary:'):
+ i -= 1
+ i += 1 # Ignore the "Warnings summary:" line
+ while i < -1:
+ warnings['%s:%d' % (builder_name, build_num)].append(stdio[i])
+ i += 1
+ except IndexError:
+ # Some builds don't have any
+ print ' No warnings section found.'
+ # sharedwarnings will build up the intersection of all the lists of
+ # warnings. We seed it with an arbitrary starting point (which is fine
+ # since intersection is commutative).
+ sharedwarnings = set(warnings.popitem()[1])
+ for warning_list in warnings.itervalues():
+ sharedwarnings = sharedwarnings & set(warning_list)
+ print 'Warnings shared across all builders:'
+ for warning in sorted(list(sharedwarnings)):
+ print warning
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/libcore_failures.txt b/tools/libcore_failures.txt
index 08abdb3..6529640 100644
--- a/tools/libcore_failures.txt
+++ b/tools/libcore_failures.txt
@@ -105,12 +105,6 @@
names: ["org.apache.harmony.tests.java.lang.ProcessTest#test_getErrorStream"]
},
{
- description: "Error decoding digital signature bytes.",
- result: EXEC_FAILED,
- name: "org.apache.harmony.security.tests.java.security.Signature2Test#test_verify$BII",
- bug: 18869265
-},
-{
description: "Test sometimes timeouts on volantis, and on most modes in debug mode",
result: EXEC_TIMEOUT,
names: ["libcore.java.lang.SystemTest#testArrayCopyConcurrentModification"],