diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5973a95e1ffa1adc099e3e1af4c372add9aca778 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +USE_LLVM_COMPILER diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000000000000000000000000000000000000..feddd98c27d24da0b5e9428dfc06a17f1e24c7e3 --- /dev/null +++ b/Android.mk @@ -0,0 +1,359 @@ +# +# Copyright (C) 2011 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) + +# These can be overridden via the environment or by editing to +# enable/disable certain build configuration. +# +# For example, to disable everything but the host debug build you use: +# +# (export ART_BUILD_TARGET_NDEBUG=false && export ART_BUILD_TARGET_DEBUG=false && export ART_BUILD_HOST_NDEBUG=false && ...) +# +# Beware that tests may use the non-debug build for performance, notable 055-enum-performance +# +ART_BUILD_TARGET_NDEBUG ?= true +ART_BUILD_TARGET_DEBUG ?= true +ART_BUILD_HOST_NDEBUG ?= true +ART_BUILD_HOST_DEBUG ?= true + +ifeq ($(ART_BUILD_TARGET_NDEBUG),false) +$(info Disabling ART_BUILD_TARGET_NDEBUG) +endif +ifeq ($(ART_BUILD_TARGET_DEBUG),false) +$(info Disabling ART_BUILD_TARGET_DEBUG) +endif +ifeq ($(ART_BUILD_HOST_NDEBUG),false) +$(info Disabling ART_BUILD_HOST_NDEBUG) +endif +ifeq ($(ART_BUILD_HOST_DEBUG),false) +$(info Disabling ART_BUILD_HOST_DEBUG) +endif + +ART_HOST_SHLIB_EXTENSION := $(HOST_SHLIB_SUFFIX) +ART_HOST_SHLIB_EXTENSION ?= .so + +build_path := $(LOCAL_PATH)/build +include $(build_path)/Android.common.mk + +######################################################################## +# clean-oat targets +# + +# following the example of build's dont_bother for clean targets +ifneq (,$(filter clean-oat,$(MAKECMDGOALS))) +art_dont_bother := true +endif +ifneq (,$(filter clean-oat-host,$(MAKECMDGOALS))) +art_dont_bother := true +endif +ifneq (,$(filter clean-oat-target,$(MAKECMDGOALS))) +art_dont_bother := true +endif + +.PHONY: clean-oat +clean-oat: clean-oat-host clean-oat-target + +.PHONY: clean-oat-host +clean-oat-host: + rm -f $(ART_NATIVETEST_OUT)/*.oat + rm -f $(ART_NATIVETEST_OUT)/*.art + rm -f $(ART_TEST_OUT)/*.oat + rm -f $(ART_TEST_OUT)/*.art + rm -f $(ART_CACHE_OUT)/*.oat + rm -f $(ART_CACHE_OUT)/*.art + rm -f $(HOST_OUT_JAVA_LIBRARIES)/*.oat + rm -f $(HOST_OUT_JAVA_LIBRARIES)/*.art + rm -f $(TARGET_OUT_JAVA_LIBRARIES)/*.oat + rm -f $(TARGET_OUT_JAVA_LIBRARIES)/*.art + rm -f $(TARGET_OUT_UNSTRIPPED)/system/framework/*.oat + rm -f $(TARGET_OUT_APPS)/*.oat + rm -f $(TARGET_OUT_INTERMEDIATES)/JAVA_LIBRARIES/*_intermediates/javalib.jar.oat + rm -f $(TARGET_OUT_INTERMEDIATES)/APPS/*_intermediates/*.apk.oat + rm -rf /tmp/test-*/art-cache/*.oat + +.PHONY: clean-oat-target +clean-oat-target: + adb remount + adb shell rm $(ART_NATIVETEST_DIR)/*.oat + adb shell rm $(ART_NATIVETEST_DIR)/*.art + adb shell rm $(ART_TEST_DIR)/*.oat + adb shell rm $(ART_TEST_DIR)/*.art + adb shell rm $(ART_CACHE_DIR)/*.oat + adb shell rm $(ART_CACHE_DIR)/*.art + adb shell rm $(DEXPREOPT_BOOT_JAR_DIR)/*.oat + adb shell rm $(DEXPREOPT_BOOT_JAR_DIR)/*.art + adb shell rm system/app/*.oat + adb shell rm data/run-test/test-*/art-cache/*.oat + +ifeq ($(HOST_OS)-$(HOST_ARCH),darwin-x86) +art_dont_bother := true +endif + +ifneq ($(art_dont_bother),true) + +######################################################################## +# product targets +include $(build_path)/Android.libart.mk +include $(build_path)/Android.libart-compiler.mk +include $(build_path)/Android.executable.mk +include $(build_path)/Android.oat.mk + +# ART_HOST_DEPENDENCIES depends on Android.executable.mk above for ART_HOST_EXECUTABLES +ART_HOST_DEPENDENCIES := $(ART_HOST_EXECUTABLES) $(HOST_OUT_JAVA_LIBRARIES)/core-hostdex.jar +ART_HOST_DEPENDENCIES += $(HOST_OUT_SHARED_LIBRARIES)/libjavacore$(ART_HOST_SHLIB_EXTENSION) +ART_TARGET_DEPENDENCIES := $(ART_TARGET_EXECUTABLES) $(TARGET_OUT_JAVA_LIBRARIES)/core.jar $(TARGET_OUT_SHARED_LIBRARIES)/libjavacore.so + +######################################################################## +# test targets + +include $(build_path)/Android.oattest.mk +include $(build_path)/Android.gtest.mk + +# The ART_*_TEST_DEPENDENCIES definitions: +# - depend on Android.oattest.mk above for ART_TEST_*_DEX_FILES +# - depend on Android.gtest.mk above for ART_*_TEST_EXECUTABLES +ART_HOST_TEST_DEPENDENCIES := $(ART_HOST_DEPENDENCIES) $(ART_HOST_TEST_EXECUTABLES) $(ART_TEST_HOST_DEX_FILES) $(HOST_CORE_IMG_OUT) +ART_TARGET_TEST_DEPENDENCIES := $(ART_TARGET_DEPENDENCIES) $(ART_TARGET_TEST_EXECUTABLES) $(ART_TEST_TARGET_DEX_FILES) $(TARGET_CORE_IMG_OUT) + +include $(build_path)/Android.libarttest.mk + +# "m build-art" for quick minimal build +.PHONY: build-art +build-art: \ + $(ART_TARGET_EXECUTABLES) \ + $(ART_TARGET_TEST_EXECUTABLES) \ + $(ART_HOST_EXECUTABLES) \ + $(ART_HOST_TEST_EXECUTABLES) + +# "mm test-art" to build and run all tests on host and device +.PHONY: test-art +test-art: test-art-host test-art-target + @echo test-art PASSED + +.PHONY: test-art-gtest +test-art-gtest: test-art-host-gtest test-art-target-gtest + @echo test-art-gtest PASSED + +.PHONY: test-art-oat +test-art-oat: test-art-host-oat test-art-target-oat test-art-host-interpreter-oat + @echo test-art-oat PASSED + +.PHONY: test-art-run-test +test-art-run-test: test-art-host-run-test test-art-target-run-test test-art-host-interpreter-run-test + @echo test-art-run-test PASSED + +######################################################################## +# host test targets + +# "mm test-art-host" to build and run all host tests +.PHONY: test-art-host +test-art-host: test-art-host-gtest test-art-host-oat test-art-host-run-test test-art-host-interpreter + @echo test-art-host PASSED + +.PHONY: test-art-host-interpreter +test-art-host-interpreter: test-art-host-interpreter-oat test-art-host-interpreter-run-test + @echo test-art-host-interpreter PASSED + +.PHONY: test-art-host-dependencies +test-art-host-dependencies: $(ART_HOST_TEST_DEPENDENCIES) $(HOST_OUT_SHARED_LIBRARIES)/libarttest$(ART_HOST_SHLIB_EXTENSION) $(HOST_CORE_DEX_LOCATIONS) + +.PHONY: test-art-host-gtest +test-art-host-gtest: $(ART_HOST_TEST_TARGETS) + @echo test-art-host-gtest PASSED + +define run-host-gtests-with + $(foreach file,$(sort $(ART_HOST_TEST_EXECUTABLES)),$(1) $(file) &&) true +endef + +# "mm valgrind-test-art-host-gtest" to build and run the host gtests under valgrind. +.PHONY: valgrind-test-art-host-gtest +valgrind-test-art-host-gtest: test-art-host-dependencies + $(call run-host-gtests-with,valgrind --leak-check=full) + @echo valgrind-test-art-host-gtest PASSED + +.PHONY: test-art-host-oat +test-art-host-oat: $(ART_TEST_HOST_OAT_TARGETS) + @echo test-art-host-oat PASSED + +.PHONY: test-art-host-interpreter-oat +test-art-host-interpreter-oat: $(ART_TEST_HOST_INTERPRETER_OAT_TARGETS) + @echo test-art-host-interpreter-oat PASSED + +define declare-test-art-host-run-test +.PHONY: test-art-host-run-test-$(1) +test-art-host-run-test-$(1): test-art-host-dependencies + art/test/run-test --host $(1) + @echo test-art-host-run-test-$(1) PASSED + +TEST_ART_HOST_RUN_TEST_TARGETS += test-art-host-run-test-$(1) + +.PHONY: test-art-host-interpreter-run-test-$(1) +test-art-host-interpreter-run-test-$(1): test-art-host-dependencies + art/test/run-test --host --interpreter $(1) + @echo test-art-host-interpreter-run-test-$(1) PASSED + +TEST_ART_HOST_INTERPRETER_RUN_TEST_TARGETS += test-art-host-interpreter-run-test-$(1) +endef + +$(foreach test, $(wildcard art/test/[0-9]*), $(eval $(call declare-test-art-host-run-test,$(notdir $(test))))) + +.PHONY: test-art-host-run-test +test-art-host-run-test: $(TEST_ART_HOST_RUN_TEST_TARGETS) + @echo test-art-host-run-test PASSED + +.PHONY: test-art-host-interpreter-run-test +test-art-host-interpreter-run-test: $(TEST_ART_HOST_INTERPRETER_RUN_TEST_TARGETS) + @echo test-art-host-interpreter-run-test PASSED + +######################################################################## +# target test targets + +# "mm test-art-target" to build and run all target tests +.PHONY: test-art-target +test-art-target: test-art-target-gtest test-art-target-oat test-art-target-run-test + @echo test-art-target PASSED + +.PHONY: test-art-target-dependencies +test-art-target-dependencies: $(ART_TARGET_TEST_DEPENDENCIES) $(ART_TEST_OUT)/libarttest.so + +.PHONY: test-art-target-sync +test-art-target-sync: test-art-target-dependencies + adb remount + adb sync + adb shell mkdir -p $(ART_TEST_DIR) + +.PHONY: test-art-target-gtest +test-art-target-gtest: $(ART_TARGET_TEST_TARGETS) + +.PHONY: test-art-target-oat +test-art-target-oat: $(ART_TEST_TARGET_OAT_TARGETS) + @echo test-art-target-oat PASSED + +define declare-test-art-target-run-test +.PHONY: test-art-target-run-test-$(1) +test-art-target-run-test-$(1): test-art-target-sync + art/test/run-test $(1) + @echo test-art-target-run-test-$(1) PASSED + +TEST_ART_TARGET_RUN_TEST_TARGETS += test-art-target-run-test-$(1) +endef + +$(foreach test, $(wildcard art/test/[0-9]*), $(eval $(call declare-test-art-target-run-test,$(notdir $(test))))) + +.PHONY: test-art-target-run-test +test-art-target-run-test: $(TEST_ART_TARGET_RUN_TEST_TARGETS) + @echo test-art-target-run-test PASSED + +######################################################################## +# oat-target and oat-target-sync targets + +OAT_TARGET_TARGETS := + +# $(1): input jar or apk target location +define declare-oat-target-target +ifneq (,$(filter $(1),$(addprefix system/app/,$(addsuffix .apk,$(PRODUCT_DEX_PREOPT_PACKAGES_IN_DATA))))) +OUT_OAT_FILE := $(call art-cache-out,$(1).oat) +else +OUT_OAT_FILE := $(PRODUCT_OUT)/$(1).oat +endif + +ifeq ($(ONE_SHOT_MAKEFILE),) +# ONE_SHOT_MAKEFILE is empty for a top level build and we don't want +# to define the oat-target-* rules there because they will conflict +# with the build/core/dex_preopt.mk defined rules. +.PHONY: oat-target-$(1) +oat-target-$(1): + +else +.PHONY: oat-target-$(1) +oat-target-$(1): $$(OUT_OAT_FILE) + +$$(OUT_OAT_FILE): $(PRODUCT_OUT)/$(1) $(TARGET_BOOT_IMG_OUT) $(DEX2OAT_DEPENDENCY) + @mkdir -p $$(dir $$@) + $(DEX2OAT) $(PARALLEL_ART_COMPILE_JOBS) --runtime-arg -Xms64m --runtime-arg -Xmx64m --boot-image=$(TARGET_BOOT_IMG_OUT) --dex-file=$(PRODUCT_OUT)/$(1) --dex-location=/$(1) --oat-file=$$@ --host-prefix=$(PRODUCT_OUT) --instruction-set=$(TARGET_ARCH) --android-root=$(PRODUCT_OUT)/system + +endif + +OAT_TARGET_TARGETS += oat-target-$(1) +endef + +$(foreach file,\ + $(filter-out\ + $(addprefix $(TARGET_OUT_JAVA_LIBRARIES)/,$(addsuffix .jar,$(TARGET_BOOT_JARS))),\ + $(wildcard $(TARGET_OUT_APPS)/*.apk) $(wildcard $(TARGET_OUT_JAVA_LIBRARIES)/*.jar)),\ + $(eval $(call declare-oat-target-target,$(subst $(PRODUCT_OUT)/,,$(file))))) + +.PHONY: oat-target +oat-target: $(ART_TARGET_DEPENDENCIES) $(TARGET_BOOT_OAT_OUT) $(OAT_TARGET_TARGETS) + +.PHONY: oat-target-sync +oat-target-sync: oat-target + adb remount + adb sync + +######################################################################## +# oatdump targets + +.PHONY: dump-oat +dump-oat: dump-oat-core dump-oat-boot + +.PHONY: dump-oat-core +dump-oat-core: dump-oat-core-host dump-oat-core-target + +.PHONY: dump-oat-core-host +ifeq ($(ART_BUILD_HOST),true) +dump-oat-core-host: $(HOST_CORE_IMG_OUT) $(OATDUMP) + $(OATDUMP) --image=$(HOST_CORE_IMG_OUT) --output=/tmp/core.host.oatdump.txt --host-prefix="" + @echo Output in /tmp/core.host.oatdump.txt +endif + +.PHONY: dump-oat-core-target +ifeq ($(ART_BUILD_TARGET),true) +dump-oat-core-target: $(TARGET_CORE_IMG_OUT) $(OATDUMP) + $(OATDUMP) --image=$(TARGET_CORE_IMG_OUT) --output=/tmp/core.target.oatdump.txt + @echo Output in /tmp/core.target.oatdump.txt +endif + +.PHONY: dump-oat-boot +ifeq ($(ART_BUILD_TARGET_NDEBUG),true) +dump-oat-boot: $(TARGET_BOOT_IMG_OUT) $(OATDUMP) + $(OATDUMP) --image=$(TARGET_BOOT_IMG_OUT) --output=/tmp/boot.oatdump.txt + @echo Output in /tmp/boot.oatdump.txt +endif + +.PHONY: dump-oat-Calculator +ifeq ($(ART_BUILD_TARGET_NDEBUG),true) +dump-oat-Calculator: $(TARGET_OUT_APPS)/Calculator.apk.oat $(TARGET_BOOT_IMG_OUT) $(OATDUMP) + $(OATDUMP) --oat-file=$< --output=/tmp/Calculator.oatdump.txt + @echo Output in /tmp/Calculator.oatdump.txt +endif + +######################################################################## +# cpplint target + +# "mm cpplint-art" to style check art source files +.PHONY: cpplint-art +cpplint-art: + ./art/tools/cpplint.py \ + --filter=-whitespace/comments,-whitespace/line_length,-build/include,-build/header_guard,-readability/function,-readability/streams,-readability/todo,-runtime/references \ + $(ANDROID_BUILD_TOP)/art/src/*.h $(ANDROID_BUILD_TOP)/art/src/*.cc + +######################################################################## + +include $(call all-makefiles-under,$(LOCAL_PATH)) + +endif # !art_dont_bother diff --git a/build/Android.common.mk b/build/Android.common.mk new file mode 100644 index 0000000000000000000000000000000000000000..508ff1bb806081cd85d6aec4da0055debe15cd55 --- /dev/null +++ b/build/Android.common.mk @@ -0,0 +1,481 @@ +# +# Copyright (C) 2011 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. +# + +ART_SMALL_MODE := false +ifneq ($(wildcard art/SMALL_ART),) +$(info Enabling ART_SMALL_MODE because of existence of art/SMALL_ART) +ART_SMALL_MODE := true +endif + +ART_USE_PORTABLE_COMPILER := false +ifneq ($(wildcard art/USE_PORTABLE_COMPILER),) +$(info Enabling ART_USE_PORTABLE_COMPILER because of existence of art/USE_PORTABLE_COMPILER) +ART_USE_PORTABLE_COMPILER := true +endif +ifeq ($(WITH_ART_USE_PORTABLE_COMPILER),true) +$(info Enabling ART_USE_PORTABLE_COMPILER because WITH_ART_USE_PORTABLE_COMPILER=true) +ART_USE_PORTABLE_COMPILER := true +endif + +LLVM_ROOT_PATH := external/llvm +include $(LLVM_ROOT_PATH)/llvm.mk + +# Clang build. +# ART_TARGET_CLANG := true +# ART_HOST_CLANG := true + +# directory used for gtests on device +ART_NATIVETEST_DIR := /data/nativetest/art +ART_NATIVETEST_OUT := $(TARGET_OUT_DATA_NATIVE_TESTS)/art + +# directory used for tests on device +ART_TEST_DIR := /data/art-test +ART_TEST_OUT := $(TARGET_OUT_DATA)/art-test + +ART_CPP_EXTENSION := .cc + +ART_C_INCLUDES := \ + external/gtest/include \ + external/zlib \ + frameworks/compile/mclinker/include \ + art/src + +art_cflags := \ + -O2 \ + -ggdb3 \ + -Wall \ + -Werror \ + -Wextra \ + -Wstrict-aliasing=3 \ + -fstrict-aliasing + +# Enable thread-safety for GCC 4.6 but not for GCC 4.7 where this feature was removed. +# Enable GCC 4.6 builds with 'export TARGET_GCC_VERSION_EXP=4.6' +ifneq ($(filter 4.6 4.6.%, $(TARGET_GCC_VERSION)),) + $(info Enabling thread-safety for GCC $(TARGET_GCC_VERSION)) + art_cflags += -Wthread-safety +endif + +ifeq ($(ART_SMALL_MODE),true) + art_cflags += -DART_SMALL_MODE=1 +endif + +# TODO: enable -std=gnu++0x for auto support when on Ubuntu 12.04 LTS (Precise Pangolin) +# On 10.04 LTS (Lucid Lynx), it can cause dependencies on GLIBCXX_3.4.14 version symbols. + +ifeq ($(HOST_OS),linux) + art_non_debug_cflags := \ + -Wframe-larger-than=1728 +endif + +art_debug_cflags := \ + -fno-inline \ + -DDYNAMIC_ANNOTATIONS_ENABLED=1 \ + -UNDEBUG + +# start of image reserved address space +IMG_HOST_BASE_ADDRESS := 0x60000000 + +ifeq ($(TARGET_ARCH),mips) +IMG_TARGET_BASE_ADDRESS := 0x30000000 +else +IMG_TARGET_BASE_ADDRESS := 0x60000000 +endif + +ART_HOST_CFLAGS := $(art_cflags) -DANDROID_SMP=1 -DART_BASE_ADDRESS=$(IMG_HOST_BASE_ADDRESS) + +ifeq ($(TARGET_ARCH),x86) +ART_TARGET_CFLAGS += -msse2 +endif + +ART_TARGET_CFLAGS := $(art_cflags) -DART_TARGET -DART_BASE_ADDRESS=$(IMG_TARGET_BASE_ADDRESS) +ifeq ($(TARGET_CPU_SMP),true) + ART_TARGET_CFLAGS += -DANDROID_SMP=1 +else + ART_TARGET_CFLAGS += -DANDROID_SMP=0 +endif + +# To use oprofile_android --callgraph, uncomment this and recompile with "mmm art -B -j16" +# ART_TARGET_CFLAGS += -fno-omit-frame-pointer -marm -mapcs + +ART_HOST_NON_DEBUG_CFLAGS := $(art_non_debug_cflags) +ART_TARGET_NON_DEBUG_CFLAGS := $(art_non_debug_cflags) + +# TODO: move -fkeep-inline-functions to art_debug_cflags when target gcc > 4.4 (and -lsupc++) +ART_HOST_DEBUG_CFLAGS := $(art_debug_cflags) -fkeep-inline-functions +ART_HOST_DEBUG_LDLIBS := -lsupc++ + +ifneq ($(HOST_OS),linux) + # Some Mac OS pthread header files are broken with -fkeep-inline-functions. + ART_HOST_DEBUG_CFLAGS := $(filter-out -fkeep-inline-functions,$(ART_HOST_DEBUG_CFLAGS)) + # Mac OS doesn't have libsupc++. + ART_HOST_DEBUG_LDLIBS := $(filter-out -lsupc++,$(ART_HOST_DEBUG_LDLIBS)) +endif + +ART_TARGET_DEBUG_CFLAGS := $(art_debug_cflags) + +ifeq ($(ART_USE_PORTABLE_COMPILER),true) +PARALLEL_ART_COMPILE_JOBS := -j8 +endif + +DEX2OAT_SRC_FILES := \ + src/dex2oat.cc + +OATDUMP_SRC_FILES := \ + src/oatdump.cc + +OATEXEC_SRC_FILES := \ + src/oatexec.cc + +LIBART_COMMON_SRC_FILES := \ + src/atomic.cc.arm \ + src/barrier.cc \ + src/base/logging.cc \ + src/base/mutex.cc \ + src/base/stringpiece.cc \ + src/base/stringprintf.cc \ + src/base/timing_logger.cc \ + src/base/unix_file/fd_file.cc \ + src/base/unix_file/mapped_file.cc \ + src/base/unix_file/null_file.cc \ + src/base/unix_file/random_access_file_utils.cc \ + src/base/unix_file/string_file.cc \ + src/check_jni.cc \ + src/class_linker.cc \ + src/common_throws.cc \ + src/compiled_method.cc \ + src/compiler/driver/compiler_driver.cc \ + src/compiler/llvm/runtime_support_llvm.cc \ + src/debugger.cc \ + src/dex_file.cc \ + src/dex_file_verifier.cc \ + src/dex_instruction.cc \ + src/disassembler.cc \ + src/disassembler_arm.cc \ + src/disassembler_mips.cc \ + src/disassembler_x86.cc \ + src/dlmalloc.cc \ + src/elf_file.cc \ + src/file_output_stream.cc \ + src/gc/card_table.cc \ + src/gc/garbage_collector.cc \ + src/gc/heap_bitmap.cc \ + src/gc/large_object_space.cc \ + src/gc/mark_sweep.cc \ + src/gc/mod_union_table.cc \ + src/gc/partial_mark_sweep.cc \ + src/gc/space.cc \ + src/gc/space_bitmap.cc \ + src/gc/sticky_mark_sweep.cc \ + src/heap.cc \ + src/hprof/hprof.cc \ + src/image.cc \ + src/image_writer.cc \ + src/indirect_reference_table.cc \ + src/instrumentation.cc \ + src/intern_table.cc \ + src/interpreter/interpreter.cc \ + src/jdwp/jdwp_event.cc \ + src/jdwp/jdwp_expand_buf.cc \ + src/jdwp/jdwp_handler.cc \ + src/jdwp/jdwp_main.cc \ + src/jdwp/jdwp_request.cc \ + src/jdwp/jdwp_socket.cc \ + src/jdwp/object_registry.cc \ + src/jni_internal.cc \ + src/jobject_comparator.cc \ + src/locks.cc \ + src/mem_map.cc \ + src/memory_region.cc \ + src/mirror/abstract_method.cc \ + src/mirror/array.cc \ + src/mirror/class.cc \ + src/mirror/dex_cache.cc \ + src/mirror/field.cc \ + src/mirror/object.cc \ + src/mirror/stack_trace_element.cc \ + src/mirror/string.cc \ + src/mirror/throwable.cc \ + src/monitor.cc \ + src/native/dalvik_system_DexFile.cc \ + src/native/dalvik_system_VMDebug.cc \ + src/native/dalvik_system_VMRuntime.cc \ + src/native/dalvik_system_VMStack.cc \ + src/native/dalvik_system_Zygote.cc \ + src/native/java_lang_Class.cc \ + src/native/java_lang_Object.cc \ + src/native/java_lang_Runtime.cc \ + src/native/java_lang_String.cc \ + src/native/java_lang_System.cc \ + src/native/java_lang_Thread.cc \ + src/native/java_lang_Throwable.cc \ + src/native/java_lang_VMClassLoader.cc \ + src/native/java_lang_reflect_Array.cc \ + src/native/java_lang_reflect_Constructor.cc \ + src/native/java_lang_reflect_Field.cc \ + src/native/java_lang_reflect_Method.cc \ + src/native/java_lang_reflect_Proxy.cc \ + src/native/java_util_concurrent_atomic_AtomicLong.cc \ + src/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc \ + src/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc \ + src/native/sun_misc_Unsafe.cc \ + src/oat.cc \ + src/oat/utils/arm/assembler_arm.cc \ + src/oat/utils/arm/managed_register_arm.cc \ + src/oat/utils/assembler.cc \ + src/oat/utils/mips/assembler_mips.cc \ + src/oat/utils/mips/managed_register_mips.cc \ + src/oat/utils/x86/assembler_x86.cc \ + src/oat/utils/x86/managed_register_x86.cc \ + src/oat_file.cc \ + src/oat_writer.cc \ + src/offsets.cc \ + src/os_linux.cc \ + src/primitive.cc \ + src/reference_table.cc \ + src/reflection.cc \ + src/runtime.cc \ + src/runtime_support.cc \ + src/signal_catcher.cc \ + src/stack.cc \ + src/thread.cc \ + src/thread_list.cc \ + src/thread_pool.cc \ + src/throw_location.cc \ + src/trace.cc \ + src/utf.cc \ + src/utils.cc \ + src/vector_output_stream.cc \ + src/verifier/dex_gc_map.cc \ + src/verifier/instruction_flags.cc \ + src/verifier/method_verifier.cc \ + src/verifier/reg_type.cc \ + src/verifier/reg_type_cache.cc \ + src/verifier/register_line.cc \ + src/well_known_classes.cc \ + src/zip_archive.cc + +LIBART_COMMON_SRC_FILES += \ + src/oat/runtime/context.cc \ + src/oat/runtime/support_alloc.cc \ + src/oat/runtime/support_cast.cc \ + src/oat/runtime/support_deoptimize.cc \ + src/oat/runtime/support_dexcache.cc \ + src/oat/runtime/support_field.cc \ + src/oat/runtime/support_fillarray.cc \ + src/oat/runtime/support_instrumentation.cc \ + src/oat/runtime/support_invoke.cc \ + src/oat/runtime/support_jni.cc \ + src/oat/runtime/support_locks.cc \ + src/oat/runtime/support_math.cc \ + src/oat/runtime/support_proxy.cc \ + src/oat/runtime/support_stubs.cc \ + src/oat/runtime/support_thread.cc \ + src/oat/runtime/support_throw.cc \ + src/oat/runtime/support_interpreter.cc + +LIBART_TARGET_SRC_FILES := \ + $(LIBART_COMMON_SRC_FILES) \ + src/base/logging_android.cc \ + src/jdwp/jdwp_adb.cc \ + src/monitor_android.cc \ + src/runtime_android.cc \ + src/thread_android.cc + +ifeq ($(TARGET_ARCH),arm) +LIBART_TARGET_SRC_FILES += \ + src/oat/runtime/arm/context_arm.cc.arm \ + src/oat/runtime/arm/oat_support_entrypoints_arm.cc \ + src/oat/runtime/arm/runtime_support_arm.S +else # TARGET_ARCH != arm +ifeq ($(TARGET_ARCH),x86) +LIBART_TARGET_SRC_FILES += \ + src/oat/runtime/x86/context_x86.cc \ + src/oat/runtime/x86/oat_support_entrypoints_x86.cc \ + src/oat/runtime/x86/runtime_support_x86.S +else # TARGET_ARCH != x86 +ifeq ($(TARGET_ARCH),mips) +LIBART_TARGET_SRC_FILES += \ + src/oat/runtime/mips/context_mips.cc \ + src/oat/runtime/mips/oat_support_entrypoints_mips.cc \ + src/oat/runtime/mips/runtime_support_mips.S +else # TARGET_ARCH != mips +$(error unsupported TARGET_ARCH=$(TARGET_ARCH)) +endif # TARGET_ARCH != mips +endif # TARGET_ARCH != x86 +endif # TARGET_ARCH != arm + +ifeq ($(TARGET_ARCH),arm) +LIBART_TARGET_SRC_FILES += src/thread_arm.cc +else # TARGET_ARCH != arm +ifeq ($(TARGET_ARCH),x86) +LIBART_TARGET_SRC_FILES += src/thread_x86.cc +else # TARGET_ARCH != x86 +ifeq ($(TARGET_ARCH),mips) +LIBART_TARGET_SRC_FILES += src/thread_mips.cc +else # TARGET_ARCH != mips +$(error unsupported TARGET_ARCH=$(TARGET_ARCH)) +endif # TARGET_ARCH != mips +endif # TARGET_ARCH != x86 +endif # TARGET_ARCH != arm + +LIBART_HOST_SRC_FILES := \ + $(LIBART_COMMON_SRC_FILES) \ + src/base/logging_linux.cc \ + src/monitor_linux.cc \ + src/runtime_linux.cc \ + src/thread_linux.cc + +ifeq ($(HOST_ARCH),x86) +LIBART_HOST_SRC_FILES += \ + src/oat/runtime/x86/context_x86.cc \ + src/oat/runtime/x86/oat_support_entrypoints_x86.cc \ + src/oat/runtime/x86/runtime_support_x86.S +else # HOST_ARCH != x86 +$(error unsupported HOST_ARCH=$(HOST_ARCH)) +endif # HOST_ARCH != x86 + +ifeq ($(HOST_ARCH),x86) +LIBART_HOST_SRC_FILES += src/thread_x86.cc +else # HOST_ARCH != x86 +$(error unsupported HOST_ARCH=$(HOST_ARCH)) +endif # HOST_ARCH != x86 + + +LIBART_ENUM_OPERATOR_OUT_HEADER_FILES := \ + src/base/mutex.h \ + src/compiler/dex/compiler_enums.h \ + src/dex_file.h \ + src/dex_instruction.h \ + src/gc/gc_type.h \ + src/gc/space.h \ + src/heap.h \ + src/indirect_reference_table.h \ + src/instruction_set.h \ + src/invoke_type.h \ + src/jdwp/jdwp.h \ + src/jdwp/jdwp_constants.h \ + src/locks.h \ + src/mirror/class.h \ + src/thread.h \ + src/thread_state.h \ + src/verifier/method_verifier.h + +LIBARTTEST_COMMON_SRC_FILES := \ + test/ReferenceMap/stack_walk_refmap_jni.cc \ + test/StackWalk/stack_walk_jni.cc + +TEST_COMMON_SRC_FILES := \ + src/barrier_test.cc \ + src/base/histogram_test.cc \ + src/base/mutex_test.cc \ + src/base/unix_file/fd_file_test.cc \ + src/base/unix_file/mapped_file_test.cc \ + src/base/unix_file/null_file_test.cc \ + src/base/unix_file/random_access_file_utils_test.cc \ + src/base/unix_file/string_file_test.cc \ + src/class_linker_test.cc \ + src/compiler/driver/compiler_driver_test.cc \ + src/compiler/jni/jni_compiler_test.cc \ + src/dex_file_test.cc \ + src/dex_instruction_visitor_test.cc \ + src/dex_method_iterator_test.cc \ + src/elf_writer_test.cc \ + src/exception_test.cc \ + src/gc/space_bitmap_test.cc \ + src/gc/space_test.cc \ + src/gtest_test.cc \ + src/heap_test.cc \ + src/image_test.cc \ + src/indenter_test.cc \ + src/indirect_reference_table_test.cc \ + src/intern_table_test.cc \ + src/jni_internal_test.cc \ + src/mirror/dex_cache_test.cc \ + src/mirror/object_test.cc \ + src/oat/utils/arm/managed_register_arm_test.cc \ + src/oat/utils/x86/managed_register_x86_test.cc \ + src/oat_test.cc \ + src/output_stream_test.cc \ + src/reference_table_test.cc \ + src/runtime_support_test.cc \ + src/runtime_test.cc \ + src/thread_pool_test.cc \ + src/utils_test.cc \ + src/verifier/method_verifier_test.cc \ + src/verifier/reg_type_test.cc \ + src/zip_archive_test.cc + +TEST_TARGET_SRC_FILES := \ + $(TEST_COMMON_SRC_FILES) + +TEST_HOST_SRC_FILES := \ + $(TEST_COMMON_SRC_FILES) \ + src/oat/utils/x86/assembler_x86_test.cc + +# subdirectories of test/ which are used as inputs for gtests +TEST_DEX_DIRECTORIES := \ + AbstractMethod \ + AllFields \ + CreateMethodSignature \ + ExceptionHandle \ + Interfaces \ + Main \ + MyClass \ + MyClassNatives \ + Nested \ + NonStaticLeafMethods \ + ProtoCompare \ + ProtoCompare2 \ + StaticLeafMethods \ + Statics \ + StaticsFromCode \ + XandY + +# subdirectories of test/ which are used with test-art-target-oat +# Declare the simplest tests (Main, HelloWorld) first, the rest are alphabetical +TEST_OAT_DIRECTORIES := \ + Main \ + HelloWorld \ + \ + ParallelGC \ + ReferenceMap \ + StackWalk \ + ThreadStress + +# TODO: Enable when the StackWalk2 tests are passing +# StackWalk2 \ + +ART_BUILD_TARGET := false +ART_BUILD_HOST := false +ART_BUILD_NDEBUG := false +ART_BUILD_DEBUG := false +ifeq ($(ART_BUILD_TARGET_NDEBUG),true) + ART_BUILD_TARGET := true + ART_BUILD_NDEBUG := true +endif +ifeq ($(ART_BUILD_TARGET_DEBUG),true) + ART_BUILD_TARGET := true + ART_BUILD_DEBUG := true +endif +ifeq ($(ART_BUILD_HOST_NDEBUG),true) + ART_BUILD_HOST := true + ART_BUILD_NDEBUG := true +endif +ifeq ($(ART_BUILD_HOST_DEBUG),true) + ART_BUILD_HOST := true + ART_BUILD_DEBUG := true +endif diff --git a/build/Android.executable.mk b/build/Android.executable.mk new file mode 100644 index 0000000000000000000000000000000000000000..ee968a77f27b03c56e606d497a1c83ab6e9e0042 --- /dev/null +++ b/build/Android.executable.mk @@ -0,0 +1,128 @@ +# +# Copyright (C) 2011 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. +# + +ART_HOST_EXECUTABLES := +ART_TARGET_EXECUTABLES := + +ART_EXECUTABLES_CFLAGS := +ifeq ($(ART_USE_PORTABLE_COMPILER),true) + ART_EXECUTABLES_CFLAGS += -DART_USE_PORTABLE_COMPILER=1 +endif + +# $(1): executable ("d" will be appended for debug version) +# $(2): source +# $(3): target or host +# $(4): ndebug or debug +define build-art-executable + ifneq ($(3),target) + ifneq ($(3),host) + $$(error expected target or host for argument 3, received $(3)) + endif + endif + ifneq ($(4),ndebug) + ifneq ($(4),debug) + $$(error expected ndebug or debug for argument 4, received $(4)) + endif + endif + + art_executable := $(1) + art_source := $(2) + art_target_or_host := $(3) + art_ndebug_or_debug := $(4) + + include $(CLEAR_VARS) + ifeq ($$(art_target_or_host),target) + include external/stlport/libstlport.mk + endif + + LOCAL_CPP_EXTENSION := $(ART_CPP_EXTENSION) + LOCAL_MODULE_TAGS := optional + LOCAL_SRC_FILES := $$(art_source) + LOCAL_C_INCLUDES += $(ART_C_INCLUDES) + LOCAL_SHARED_LIBRARIES := libnativehelper + + ifeq ($$(art_ndebug_or_debug),ndebug) + LOCAL_MODULE := $$(art_executable) + else #debug + LOCAL_MODULE := $$(art_executable)d + endif + + LOCAL_CFLAGS := $(ART_EXECUTABLES_CFLAGS) + ifeq ($$(art_target_or_host),target) + LOCAL_CLANG := $(ART_TARGET_CLANG) + LOCAL_CFLAGS += $(ART_TARGET_CFLAGS) + ifeq ($$(art_ndebug_or_debug),debug) + LOCAL_CFLAGS += $(ART_TARGET_DEBUG_CFLAGS) + else + LOCAL_CFLAGS += $(ART_TARGET_NON_DEBUG_CFLAGS) + endif + else # host + LOCAL_CLANG := $(ART_HOST_CLANG) + LOCAL_CFLAGS += $(ART_HOST_CFLAGS) + ifeq ($$(art_ndebug_or_debug),debug) + LOCAL_CFLAGS += $(ART_HOST_DEBUG_CFLAGS) + else + LOCAL_CFLAGS += $(ART_HOST_NON_DEBUG_CFLAGS) + endif + endif + + ifeq ($$(art_ndebug_or_debug),ndebug) + LOCAL_SHARED_LIBRARIES += libart + else # debug + LOCAL_SHARED_LIBRARIES += libartd + endif + + ifeq ($$(art_target_or_host),target) + LOCAL_SHARED_LIBRARIES += libstlport + endif + + ifeq ($$(art_target_or_host),target) + include $(BUILD_EXECUTABLE) + ART_TARGET_EXECUTABLES := $(ART_TARGET_EXECUTABLES) $(TARGET_OUT_EXECUTABLES)/$$(LOCAL_MODULE) + else # host + include $(BUILD_HOST_EXECUTABLE) + ART_HOST_EXECUTABLES := $(ART_HOST_EXECUTABLES) $(HOST_OUT_EXECUTABLES)/$$(LOCAL_MODULE) + endif + +endef + +ifeq ($(ART_BUILD_TARGET_NDEBUG),true) + $(eval $(call build-art-executable,dex2oat,$(DEX2OAT_SRC_FILES),target,ndebug)) + $(eval $(call build-art-executable,oatdump,$(OATDUMP_SRC_FILES),target,ndebug)) + $(eval $(call build-art-executable,oatexec,$(OATEXEC_SRC_FILES),target,ndebug)) +endif +ifeq ($(ART_BUILD_TARGET_DEBUG),true) + $(eval $(call build-art-executable,dex2oat,$(DEX2OAT_SRC_FILES),target,debug)) + $(eval $(call build-art-executable,oatdump,$(OATDUMP_SRC_FILES),target,debug)) + $(eval $(call build-art-executable,oatexec,$(OATEXEC_SRC_FILES),target,debug)) +endif + +# We always build dex2oat and dependencies, even if the host build is otherwise disabled, since they are used to cross compile for the target. +ifeq ($(ART_BUILD_NDEBUG),true) + $(eval $(call build-art-executable,dex2oat,$(DEX2OAT_SRC_FILES),host,ndebug)) +endif +ifeq ($(ART_BUILD_NDEBUG),true) + $(eval $(call build-art-executable,dex2oat,$(DEX2OAT_SRC_FILES),host,debug)) +endif + +ifeq ($(ART_BUILD_HOST_NDEBUG),true) + $(eval $(call build-art-executable,oatdump,$(OATDUMP_SRC_FILES),host,ndebug)) + $(eval $(call build-art-executable,oatexec,$(OATEXEC_SRC_FILES),host,ndebug)) +endif +ifeq ($(ART_BUILD_HOST_DEBUG),true) + $(eval $(call build-art-executable,oatdump,$(OATDUMP_SRC_FILES),host,debug)) + $(eval $(call build-art-executable,oatexec,$(OATEXEC_SRC_FILES),host,debug)) +endif diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk new file mode 100644 index 0000000000000000000000000000000000000000..f13c47e271fabbb95aaa0c16d7ab785885b25532 --- /dev/null +++ b/build/Android.gtest.mk @@ -0,0 +1,113 @@ +# +# Copyright (C) 2011 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. +# + +ART_HOST_TEST_EXECUTABLES := +ART_TARGET_TEST_EXECUTABLES := +ART_HOST_TEST_TARGETS := +ART_TARGET_TEST_TARGETS := + +ART_TEST_CFLAGS := +ifeq ($(ART_USE_PORTABLE_COMPILER),true) + ART_TEST_CFLAGS += -DART_USE_PORTABLE_COMPILER=1 +endif + +# $(1): target or host +# $(2): file name +define build-art-test + ifneq ($(1),target) + ifneq ($(1),host) + $$(error expected target or host for argument 1, received $(1)) + endif + endif + + art_target_or_host := $(1) + art_gtest_filename := $(2) + + art_gtest_name := $$(notdir $$(basename $$(art_gtest_filename))) + + include $(CLEAR_VARS) + ifeq ($$(art_target_or_host),target) + include external/stlport/libstlport.mk + endif + + LOCAL_CPP_EXTENSION := $(ART_CPP_EXTENSION) + LOCAL_MODULE := $$(art_gtest_name) + ifeq ($$(art_target_or_host),target) + LOCAL_MODULE_TAGS := tests + endif + LOCAL_SRC_FILES := $$(art_gtest_filename) src/common_test.cc + LOCAL_C_INCLUDES += $(ART_C_INCLUDES) + LOCAL_SHARED_LIBRARIES := libartd + + # Mac OS linker doesn't understand --export-dynamic. + ifneq ($(HOST_OS)-$$(art_target_or_host),darwin-host) + # Allow jni_compiler_test to find Java_MyClassNatives_bar within itself using dlopen(NULL, ...). + LOCAL_LDFLAGS := -Wl,--export-dynamic -Wl,-u,Java_MyClassNatives_bar -Wl,-u,Java_MyClassNatives_sbar + endif + + LOCAL_CFLAGS := $(ART_TEST_CFLAGS) + ifeq ($$(art_target_or_host),target) + LOCAL_CLANG := $(ART_TARGET_CLANG) + LOCAL_CFLAGS += $(ART_TARGET_CFLAGS) $(ART_TARGET_DEBUG_CFLAGS) + LOCAL_SHARED_LIBRARIES += libdl libicuuc libicui18n libnativehelper libstlport libz libcutils + LOCAL_STATIC_LIBRARIES += libgtest + LOCAL_MODULE_PATH := $(ART_NATIVETEST_OUT) + include $(LLVM_DEVICE_BUILD_MK) + include $(BUILD_EXECUTABLE) + art_gtest_exe := $$(LOCAL_MODULE_PATH)/$$(LOCAL_MODULE) + ART_TARGET_TEST_EXECUTABLES += $$(art_gtest_exe) + else # host + LOCAL_CLANG := $(ART_HOST_CLANG) + LOCAL_CFLAGS += $(ART_HOST_CFLAGS) $(ART_HOST_DEBUG_CFLAGS) + LOCAL_SHARED_LIBRARIES += libicuuc-host libicui18n-host libnativehelper libz-host + LOCAL_STATIC_LIBRARIES += libcutils + ifeq ($(HOST_OS),darwin) + # Mac OS complains about unresolved symbols if you don't include this. + LOCAL_WHOLE_STATIC_LIBRARIES := libgtest_host + endif + include $(LLVM_HOST_BUILD_MK) + include $(BUILD_HOST_EXECUTABLE) + art_gtest_exe := $(HOST_OUT_EXECUTABLES)/$$(LOCAL_MODULE) + ART_HOST_TEST_EXECUTABLES += $$(art_gtest_exe) + endif +art_gtest_target := test-art-$$(art_target_or_host)-gtest-$$(art_gtest_name) +ifeq ($$(art_target_or_host),target) +.PHONY: $$(art_gtest_target) +$$(art_gtest_target): $$(art_gtest_exe) test-art-target-sync + adb shell touch $(ART_TEST_DIR)/$$@ + adb shell rm $(ART_TEST_DIR)/$$@ + adb shell chmod 755 $(ART_NATIVETEST_DIR)/$$(notdir $$<) + adb shell sh -c "$(ART_NATIVETEST_DIR)/$$(notdir $$<) && touch $(ART_TEST_DIR)/$$@" + $(hide) (adb pull $(ART_TEST_DIR)/$$@ /tmp/ && echo $$@ PASSED) || (echo $$@ FAILED && exit 1) + $(hide) rm /tmp/$$@ + +ART_TARGET_TEST_TARGETS += $$(art_gtest_target) +else +.PHONY: $$(art_gtest_target) +$$(art_gtest_target): $$(art_gtest_exe) test-art-host-dependencies + $$< + @echo $$@ PASSED + +ART_HOST_TEST_TARGETS += $$(art_gtest_target) +endif +endef + +ifeq ($(ART_BUILD_TARGET),true) + $(foreach file,$(TEST_TARGET_SRC_FILES), $(eval $(call build-art-test,target,$(file)))) +endif +ifeq ($(ART_BUILD_HOST),true) + $(foreach file,$(TEST_HOST_SRC_FILES), $(eval $(call build-art-test,host,$(file)))) +endif diff --git a/build/Android.libart-compiler.mk b/build/Android.libart-compiler.mk new file mode 100644 index 0000000000000000000000000000000000000000..4452f05f5251854caa1e1a6d462175e1037431d7 --- /dev/null +++ b/build/Android.libart-compiler.mk @@ -0,0 +1,212 @@ +# +# Copyright (C) 2012 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LIBART_COMPILER_SRC_FILES := \ + src/compiler/dex/local_value_numbering.cc \ + src/compiler/dex/arena_allocator.cc \ + src/compiler/dex/arena_bit_vector.cc \ + src/compiler/dex/quick/arm/assemble_arm.cc \ + src/compiler/dex/quick/arm/call_arm.cc \ + src/compiler/dex/quick/arm/fp_arm.cc \ + src/compiler/dex/quick/arm/int_arm.cc \ + src/compiler/dex/quick/arm/target_arm.cc \ + src/compiler/dex/quick/arm/utility_arm.cc \ + src/compiler/dex/quick/codegen_util.cc \ + src/compiler/dex/quick/gen_common.cc \ + src/compiler/dex/quick/gen_invoke.cc \ + src/compiler/dex/quick/gen_loadstore.cc \ + src/compiler/dex/quick/local_optimizations.cc \ + src/compiler/dex/quick/mips/assemble_mips.cc \ + src/compiler/dex/quick/mips/call_mips.cc \ + src/compiler/dex/quick/mips/fp_mips.cc \ + src/compiler/dex/quick/mips/int_mips.cc \ + src/compiler/dex/quick/mips/target_mips.cc \ + src/compiler/dex/quick/mips/utility_mips.cc \ + src/compiler/dex/quick/mir_to_lir.cc \ + src/compiler/dex/quick/ralloc_util.cc \ + src/compiler/dex/quick/x86/assemble_x86.cc \ + src/compiler/dex/quick/x86/call_x86.cc \ + src/compiler/dex/quick/x86/fp_x86.cc \ + src/compiler/dex/quick/x86/int_x86.cc \ + src/compiler/dex/quick/x86/target_x86.cc \ + src/compiler/dex/quick/x86/utility_x86.cc \ + src/compiler/dex/portable/mir_to_gbc.cc \ + src/compiler/dex/mir_dataflow.cc \ + src/compiler/dex/dataflow_iterator.cc \ + src/compiler/dex/mir_optimization.cc \ + src/compiler/dex/frontend.cc \ + src/compiler/dex/mir_graph.cc \ + src/compiler/dex/vreg_analysis.cc \ + src/compiler/dex/ssa_transformation.cc \ + src/compiler/dex/write_elf.cc \ + src/compiler/driver/dex_compilation_unit.cc \ + src/compiler/jni/portable/jni_compiler.cc \ + src/compiler/jni/quick/arm/calling_convention_arm.cc \ + src/compiler/jni/quick/mips/calling_convention_mips.cc \ + src/compiler/jni/quick/x86/calling_convention_x86.cc \ + src/compiler/jni/quick/calling_convention.cc \ + src/compiler/jni/quick/jni_compiler.cc \ + src/compiler/llvm/compiler_llvm.cc \ + src/compiler/llvm/gbc_expander.cc \ + src/compiler/llvm/generated/art_module.cc \ + src/compiler/llvm/intrinsic_helper.cc \ + src/compiler/llvm/ir_builder.cc \ + src/compiler/llvm/llvm_compilation_unit.cc \ + src/compiler/llvm/md_builder.cc \ + src/compiler/llvm/runtime_support_builder.cc \ + src/compiler/llvm/runtime_support_builder_arm.cc \ + src/compiler/llvm/runtime_support_builder_thumb2.cc \ + src/compiler/llvm/runtime_support_builder_x86.cc \ + src/compiler/llvm/runtime_support_llvm.cc \ + src/elf_fixup.cc \ + src/elf_stripper.cc \ + src/elf_writer.cc \ + src/elf_writer_quick.cc + +LIBART_COMPILER_CFLAGS := +ifeq ($(ART_USE_PORTABLE_COMPILER),true) + LIBART_COMPILER_SRC_FILES += src/elf_writer_mclinker.cc + LIBART_COMPILER_CFLAGS += -DART_USE_PORTABLE_COMPILER=1 +endif + +# $(1): target or host +# $(2): ndebug or debug +define build-libart-compiler + ifneq ($(1),target) + ifneq ($(1),host) + $$(error expected target or host for argument 1, received $(1)) + endif + endif + ifneq ($(2),ndebug) + ifneq ($(2),debug) + $$(error expected ndebug or debug for argument 2, received $(2)) + endif + endif + + art_target_or_host := $(1) + art_ndebug_or_debug := $(2) + + include $(CLEAR_VARS) + ifeq ($$(art_target_or_host),target) + include external/stlport/libstlport.mk + endif + LOCAL_CPP_EXTENSION := $(ART_CPP_EXTENSION) + ifeq ($$(art_ndebug_or_debug),ndebug) + LOCAL_MODULE := libart-compiler + else # debug + LOCAL_MODULE := libartd-compiler + endif + + LOCAL_MODULE_TAGS := optional + LOCAL_MODULE_CLASS := SHARED_LIBRARIES + + LOCAL_SRC_FILES := $$(LIBART_COMPILER_SRC_FILES) + + LOCAL_CFLAGS := $$(LIBART_COMPILER_CFLAGS) + ifeq ($$(art_target_or_host),target) + LOCAL_CLANG := $(ART_TARGET_CLANG) + LOCAL_CFLAGS += $(ART_TARGET_CFLAGS) + else # host + LOCAL_CLANG := $(ART_HOST_CLANG) + LOCAL_CFLAGS += $(ART_HOST_CFLAGS) + endif + + # TODO: clean up the compilers and remove this. + LOCAL_CFLAGS += -Wno-unused-parameter + + LOCAL_SHARED_LIBRARIES := liblog + ifeq ($$(art_ndebug_or_debug),debug) + ifeq ($$(art_target_or_host),target) + LOCAL_CFLAGS += $(ART_TARGET_DEBUG_CFLAGS) + else # host + LOCAL_CFLAGS += $(ART_HOST_DEBUG_CFLAGS) + endif + LOCAL_SHARED_LIBRARIES += libartd + else + ifeq ($$(art_target_or_host),target) + LOCAL_CFLAGS += $(ART_TARGET_NON_DEBUG_CFLAGS) + else # host + LOCAL_CFLAGS += $(ART_HOST_NON_DEBUG_CFLAGS) + endif + LOCAL_SHARED_LIBRARIES += libart + endif + LOCAL_SHARED_LIBRARIES += libbcc libLLVM + + ifeq ($(ART_USE_PORTABLE_COMPILER),true) + LOCAL_CFLAGS += -DART_USE_PORTABLE_COMPILER=1 + endif + + LOCAL_C_INCLUDES += $(ART_C_INCLUDES) + + ifeq ($$(art_target_or_host),target) + LOCAL_SHARED_LIBRARIES += libstlport + else # host + LOCAL_LDLIBS := -ldl -lpthread + endif + ifeq ($$(art_target_or_host),target) + LOCAL_SHARED_LIBRARIES += libcutils + include $(LLVM_GEN_INTRINSICS_MK) + include $(LLVM_DEVICE_BUILD_MK) + include $(BUILD_SHARED_LIBRARY) + else # host + LOCAL_IS_HOST_MODULE := true + LOCAL_STATIC_LIBRARIES += libcutils + include $(LLVM_GEN_INTRINSICS_MK) + include $(LLVM_HOST_BUILD_MK) + include $(BUILD_HOST_SHARED_LIBRARY) + endif + + ifeq ($$(art_target_or_host),target) + ifeq ($$(art_ndebug_or_debug),debug) + $(TARGET_OUT_EXECUTABLES)/dex2oatd: $$(LOCAL_INSTALLED_MODULE) + else + $(TARGET_OUT_EXECUTABLES)/dex2oat: $$(LOCAL_INSTALLED_MODULE) + endif + else # host + ifeq ($$(art_ndebug_or_debug),debug) + $(HOST_OUT_EXECUTABLES)/dex2oatd: $$(LOCAL_INSTALLED_MODULE) + else + $(HOST_OUT_EXECUTABLES)/dex2oat: $$(LOCAL_INSTALLED_MODULE) + endif + endif + +endef + +ifeq ($(ART_BUILD_TARGET_NDEBUG),true) + $(eval $(call build-libart-compiler,target,ndebug)) +endif +ifeq ($(ART_BUILD_TARGET_DEBUG),true) + $(eval $(call build-libart-compiler,target,debug)) +endif +# We always build dex2oat and dependencies, even if the host build is otherwise disabled, since they are used to cross compile for the target. +ifeq ($(ART_BUILD_NDEBUG),true) + $(eval $(call build-libart-compiler,host,ndebug)) +endif +ifeq ($(ART_BUILD_DEBUG),true) + $(eval $(call build-libart-compiler,host,debug)) +endif + +# Rule to build /system/lib/libcompiler_rt.a +# Usually static libraries are not installed on the device. +ifeq ($(ART_USE_PORTABLE_COMPILER),true) +ifeq ($(ART_BUILD_TARGET),true) +# TODO: Move to external/compiler_rt +$(eval $(call copy-one-file, $(call intermediates-dir-for,STATIC_LIBRARIES,libcompiler_rt,,)/libcompiler_rt.a, $(TARGET_OUT_SHARED_LIBRARIES)/libcompiler_rt.a)) + +$(DEX2OAT): $(TARGET_OUT_SHARED_LIBRARIES)/libcompiler_rt.a + +endif +endif diff --git a/build/Android.libart.mk b/build/Android.libart.mk new file mode 100644 index 0000000000000000000000000000000000000000..74e9b2163e8b89e745225749f99330c96b2f3bc8 --- /dev/null +++ b/build/Android.libart.mk @@ -0,0 +1,130 @@ +# +# Copyright (C) 2011 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. +# + +LIBART_CFLAGS := +ifeq ($(ART_USE_PORTABLE_COMPILER),true) + LIBART_CFLAGS += -DART_USE_PORTABLE_COMPILER=1 +endif + +# $(1): target or host +# $(2): ndebug or debug +define build-libart + ifneq ($(1),target) + ifneq ($(1),host) + $$(error expected target or host for argument 1, received $(1)) + endif + endif + ifneq ($(2),ndebug) + ifneq ($(2),debug) + $$(error expected ndebug or debug for argument 2, received $(2)) + endif + endif + + art_target_or_host := $(1) + art_ndebug_or_debug := $(2) + + include $(CLEAR_VARS) + ifeq ($$(art_target_or_host),target) + include external/stlport/libstlport.mk + endif + LOCAL_CPP_EXTENSION := $(ART_CPP_EXTENSION) + ifeq ($$(art_ndebug_or_debug),ndebug) + LOCAL_MODULE := libart + else # debug + LOCAL_MODULE := libartd + endif + + LOCAL_MODULE_TAGS := optional + LOCAL_MODULE_CLASS := SHARED_LIBRARIES + + ifeq ($$(art_target_or_host),target) + LOCAL_SRC_FILES := $(LIBART_TARGET_SRC_FILES) + else # host + LOCAL_SRC_FILES := $(LIBART_HOST_SRC_FILES) + LOCAL_IS_HOST_MODULE := true + endif + + GENERATED_SRC_DIR := $$(call intermediates-dir-for,$$(LOCAL_MODULE_CLASS),$$(LOCAL_MODULE),$$(LOCAL_IS_HOST_MODULE),) + ENUM_OPERATOR_OUT_CC_FILES := $$(patsubst %.h,%_operator_out.cc,$$(LIBART_ENUM_OPERATOR_OUT_HEADER_FILES)) + ENUM_OPERATOR_OUT_GEN := $$(addprefix $$(GENERATED_SRC_DIR)/,$$(ENUM_OPERATOR_OUT_CC_FILES)) + +$$(ENUM_OPERATOR_OUT_GEN): art/tools/generate-operator-out.py +$$(ENUM_OPERATOR_OUT_GEN): PRIVATE_CUSTOM_TOOL = art/tools/generate-operator-out.py $$< > $$@ +$$(ENUM_OPERATOR_OUT_GEN): $$(GENERATED_SRC_DIR)/%_operator_out.cc : art/%.h + $$(transform-generated-source) + + LOCAL_GENERATED_SOURCES += $$(ENUM_OPERATOR_OUT_GEN) + + LOCAL_CFLAGS := $(LIBART_CFLAGS) + ifeq ($$(art_target_or_host),target) + LOCAL_CLANG := $(ART_TARGET_CLANG) + LOCAL_CFLAGS += $(ART_TARGET_CFLAGS) + else # host + LOCAL_CLANG := $(ART_HOST_CLANG) + LOCAL_CFLAGS += $(ART_HOST_CFLAGS) + endif + ifeq ($$(art_ndebug_or_debug),debug) + ifeq ($$(art_target_or_host),target) + LOCAL_CFLAGS += $(ART_TARGET_DEBUG_CFLAGS) + else # host + LOCAL_CFLAGS += $(ART_HOST_DEBUG_CFLAGS) + LOCAL_LDLIBS += $(ART_HOST_DEBUG_LDLIBS) + LOCAL_STATIC_LIBRARIES := libgtest_host + endif + else + ifeq ($$(art_target_or_host),target) + LOCAL_CFLAGS += $(ART_TARGET_NON_DEBUG_CFLAGS) + else # host + LOCAL_CFLAGS += $(ART_HOST_NON_DEBUG_CFLAGS) + endif + endif + LOCAL_C_INCLUDES += $(ART_C_INCLUDES) + LOCAL_SHARED_LIBRARIES := liblog libnativehelper + LOCAL_SHARED_LIBRARIES += libcorkscrew # native stack trace support + ifeq ($$(art_target_or_host),target) + LOCAL_SHARED_LIBRARIES += libcutils libstlport libz libdl libselinux + else # host + LOCAL_STATIC_LIBRARIES += libcutils + LOCAL_SHARED_LIBRARIES += libz-host + LOCAL_LDLIBS += -ldl -lpthread + ifeq ($(HOST_OS),linux) + LOCAL_LDLIBS += -lrt + endif + endif + include $(LLVM_GEN_INTRINSICS_MK) + ifeq ($$(art_target_or_host),target) + include $(LLVM_DEVICE_BUILD_MK) + include $(BUILD_SHARED_LIBRARY) + else # host + include $(LLVM_HOST_BUILD_MK) + include $(BUILD_HOST_SHARED_LIBRARY) + endif +endef + +ifeq ($(ART_BUILD_TARGET_NDEBUG),true) + $(eval $(call build-libart,target,ndebug)) +endif +ifeq ($(ART_BUILD_TARGET_DEBUG),true) + $(eval $(call build-libart,target,debug)) +endif + +# We always build dex2oat and dependencies, even if the host build is otherwise disabled, since they are used to cross compile for the target. +ifeq ($(ART_BUILD_NDEBUG),true) + $(eval $(call build-libart,host,ndebug)) +endif +ifeq ($(ART_BUILD_DEBUG),true) + $(eval $(call build-libart,host,debug)) +endif diff --git a/build/Android.libarttest.mk b/build/Android.libarttest.mk new file mode 100644 index 0000000000000000000000000000000000000000..50b9a107ec86f303d53b20d0af2c6145d12ee9fe --- /dev/null +++ b/build/Android.libarttest.mk @@ -0,0 +1,63 @@ +# +# Copyright (C) 2011 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. +# + +# $(1): target or host +define build-libarttest + ifneq ($(1),target) + ifneq ($(1),host) + $$(error expected target or host for argument 1, received $(1)) + endif + endif + + art_target_or_host := $(1) + + include $(CLEAR_VARS) + ifeq ($$(art_target_or_host),target) + include external/stlport/libstlport.mk + endif + + LOCAL_CPP_EXTENSION := $(ART_CPP_EXTENSION) + LOCAL_MODULE := libarttest + ifeq ($$(art_target_or_host),target) + LOCAL_MODULE_TAGS := tests + endif + LOCAL_SRC_FILES := $(LIBARTTEST_COMMON_SRC_FILES) + LOCAL_SHARED_LIBRARIES := libartd + LOCAL_C_INCLUDES += $(ART_C_INCLUDES) + ifeq ($$(art_target_or_host),target) + LOCAL_CLANG := $(ART_TARGET_CLANG) + LOCAL_CFLAGS := $(ART_TARGET_CFLAGS) $(ART_TARGET_DEBUG_CFLAGS) + LOCAL_SHARED_LIBRARIES += libdl libstlport + LOCAL_STATIC_LIBRARIES := libgtest + LOCAL_MODULE_PATH := $(ART_TEST_OUT) + include $(BUILD_SHARED_LIBRARY) + else # host + LOCAL_CLANG := $(ART_HOST_CLANG) + LOCAL_CFLAGS := $(ART_HOST_CFLAGS) $(ART_HOST_DEBUG_CFLAGS) + LOCAL_LDLIBS := -ldl -lpthread + ifeq ($(HOST_OS),linux) + LOCAL_LDLIBS += -lrt + endif + include $(BUILD_HOST_SHARED_LIBRARY) + endif +endef + +ifeq ($(ART_BUILD_TARGET),true) + $(eval $(call build-libarttest,target)) +endif +ifeq ($(ART_BUILD_HOST),true) + $(eval $(call build-libarttest,host)) +endif diff --git a/build/Android.oat.mk b/build/Android.oat.mk new file mode 100644 index 0000000000000000000000000000000000000000..2daf5d0cec92fc979e683d97218b44f3867e408c --- /dev/null +++ b/build/Android.oat.mk @@ -0,0 +1,109 @@ +# +# Copyright (C) 2011 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. +# + +# DEX2OAT defined in build/core/config.mk +DEX2OATD := $(HOST_OUT_EXECUTABLES)/dex2oatd$(HOST_EXECUTABLE_SUFFIX) + +LIBART_COMPILER := $(HOST_OUT_SHARED_LIBRARIES)/libart-compiler$(HOST_SHLIB_SUFFIX) +LIBARTD_COMPILER := $(HOST_OUT_SHARED_LIBRARIES)/libartd-compiler$(HOST_SHLIB_SUFFIX) + +# TODO: for now, override with debug version for better error reporting +DEX2OAT := $(DEX2OATD) +LIBART_COMPILER := $(LIBARTD_COMPILER) + +# By default, do not run rerun dex2oat if the tool changes. +# Comment out the | to force dex2oat to rerun on after all changes. +DEX2OAT_DEPENDENCY := | +DEX2OAT_DEPENDENCY += $(DEX2OAT) +DEX2OAT_DEPENDENCY += $(LIBART_COMPILER) + +OATDUMP := $(HOST_OUT_EXECUTABLES)/oatdump$(HOST_EXECUTABLE_SUFFIX) +OATDUMPD := $(HOST_OUT_EXECUTABLES)/oatdumpd$(HOST_EXECUTABLE_SUFFIX) +# TODO: for now, override with debug version for better error reporting +OATDUMP := $(OATDUMPD) + +PRELOADED_CLASSES := frameworks/base/preloaded-classes + +######################################################################## +# A smaller libcore only oat file +TARGET_CORE_JARS := core conscrypt okhttp core-junit bouncycastle +HOST_CORE_JARS := $(addsuffix -hostdex,$(TARGET_CORE_JARS)) + +HOST_CORE_DEX_LOCATIONS := $(foreach jar,$(HOST_CORE_JARS), $(HOST_OUT_JAVA_LIBRARIES)/$(jar).jar) +TARGET_CORE_DEX_LOCATIONS := $(foreach jar,$(TARGET_CORE_JARS),/$(DEXPREOPT_BOOT_JAR_DIR)/$(jar).jar) + +HOST_CORE_DEX_FILES := $(foreach jar,$(HOST_CORE_JARS), $(call intermediates-dir-for,JAVA_LIBRARIES,$(jar),t,COMMON)/javalib.jar) +TARGET_CORE_DEX_FILES := $(foreach jar,$(TARGET_CORE_JARS),$(call intermediates-dir-for,JAVA_LIBRARIES,$(jar), ,COMMON)/javalib.jar) + +HOST_CORE_OAT := $(HOST_OUT_JAVA_LIBRARIES)/core.oat +TARGET_CORE_OAT := $(ART_TEST_DIR)/core.oat + +HOST_CORE_OAT_OUT := $(HOST_OUT_JAVA_LIBRARIES)/core.oat +TARGET_CORE_OAT_OUT := $(ART_TEST_OUT)/core.oat + +HOST_CORE_IMG_OUT := $(HOST_OUT_JAVA_LIBRARIES)/core.art +TARGET_CORE_IMG_OUT := $(ART_TEST_OUT)/core.art + +$(HOST_CORE_IMG_OUT): $(HOST_CORE_DEX_FILES) $(DEX2OAT_DEPENDENCY) + @echo "host dex2oat: $@ ($?)" + @mkdir -p $(dir $@) + $(hide) $(DEX2OAT) $(PARALLEL_ART_COMPILE_JOBS) --runtime-arg -Xms16m --runtime-arg -Xmx16m --image-classes=$(PRELOADED_CLASSES) $(addprefix --dex-file=,$(HOST_CORE_DEX_FILES)) $(addprefix --dex-location=,$(HOST_CORE_DEX_LOCATIONS)) --oat-file=$(HOST_CORE_OAT_OUT) --oat-location=$(HOST_CORE_OAT) --image=$(HOST_CORE_IMG_OUT) --base=$(IMG_HOST_BASE_ADDRESS) --instruction-set=$(HOST_ARCH) --host --android-root=$(HOST_OUT) + +$(TARGET_CORE_IMG_OUT): $(TARGET_CORE_DEX_FILES) $(DEX2OAT_DEPENDENCY) + @echo "target dex2oat: $@ ($?)" + @mkdir -p $(dir $@) + $(hide) $(DEX2OAT) $(PARALLEL_ART_COMPILE_JOBS) --runtime-arg -Xms16m --runtime-arg -Xmx16m --image-classes=$(PRELOADED_CLASSES) $(addprefix --dex-file=,$(TARGET_CORE_DEX_FILES)) $(addprefix --dex-location=,$(TARGET_CORE_DEX_LOCATIONS)) --oat-file=$(TARGET_CORE_OAT_OUT) --oat-location=$(TARGET_CORE_OAT) --image=$(TARGET_CORE_IMG_OUT) --base=$(IMG_TARGET_BASE_ADDRESS) --instruction-set=$(TARGET_ARCH) --host-prefix=$(PRODUCT_OUT) --android-root=$(PRODUCT_OUT)/system + +$(HOST_CORE_OAT_OUT): $(HOST_CORE_IMG_OUT) + +$(TARGET_CORE_OAT_OUT): $(TARGET_CORE_IMG_OUT) + +ifeq ($(ART_BUILD_HOST),true) +include $(CLEAR_VARS) +LOCAL_MODULE := core.art-host +LOCAL_MODULE_TAGS := optional +LOCAL_ADDITIONAL_DEPENDENCIES := $(HOST_CORE_IMG_OUT) +include $(BUILD_PHONY_PACKAGE) +endif + +######################################################################## +# The full system boot classpath +TARGET_BOOT_JARS := $(subst :, ,$(DEXPREOPT_BOOT_JARS)) +TARGET_BOOT_DEX_LOCATIONS := $(foreach jar,$(TARGET_BOOT_JARS),/$(DEXPREOPT_BOOT_JAR_DIR)/$(jar).jar) +TARGET_BOOT_DEX_FILES := $(foreach jar,$(TARGET_BOOT_JARS),$(call intermediates-dir-for,JAVA_LIBRARIES,$(jar),,COMMON)/javalib.jar) + +TARGET_BOOT_IMG_OUT := $(DEFAULT_DEX_PREOPT_IMAGE) +TARGET_BOOT_OAT_OUT := $(patsubst %.art,%.oat,$(TARGET_BOOT_IMG_OUT)) +TARGET_BOOT_OAT := $(subst $(PRODUCT_OUT),,$(TARGET_BOOT_OAT_OUT)) +TARGET_BOOT_OAT_UNSTRIPPED_OUT := $(TARGET_OUT_UNSTRIPPED)$(TARGET_BOOT_OAT) + +$(TARGET_BOOT_IMG_OUT): $(TARGET_BOOT_DEX_FILES) $(DEX2OAT_DEPENDENCY) + @echo "target dex2oat: $@ ($?)" + @mkdir -p $(dir $@) + @mkdir -p $(dir $(TARGET_BOOT_OAT_UNSTRIPPED_OUT)) + $(hide) $(DEX2OAT) $(PARALLEL_ART_COMPILE_JOBS) --runtime-arg -Xms256m --runtime-arg -Xmx256m --image-classes=$(PRELOADED_CLASSES) $(addprefix --dex-file=,$(TARGET_BOOT_DEX_FILES)) $(addprefix --dex-location=,$(TARGET_BOOT_DEX_LOCATIONS)) --oat-symbols=$(TARGET_BOOT_OAT_UNSTRIPPED_OUT) --oat-file=$(TARGET_BOOT_OAT_OUT) --oat-location=$(TARGET_BOOT_OAT) --image=$(TARGET_BOOT_IMG_OUT) --base=$(IMG_TARGET_BASE_ADDRESS) --instruction-set=$(TARGET_ARCH) --host-prefix=$(PRODUCT_OUT) --android-root=$(PRODUCT_OUT)/system + +$(TARGET_BOOT_OAT_UNSTRIPPED_OUT): $(TARGET_BOOT_IMG_OUT) + +$(TARGET_BOOT_OAT_OUT): $(TARGET_BOOT_OAT_UNSTRIPPED_OUT) + +ifeq ($(ART_BUILD_TARGET_NDEBUG),true) +include $(CLEAR_VARS) +LOCAL_MODULE := boot.art +LOCAL_MODULE_TAGS := optional +LOCAL_ADDITIONAL_DEPENDENCIES := $(TARGET_BOOT_IMG_OUT) $(TARGET_BOOT_OAT_OUT) +include $(BUILD_PHONY_PACKAGE) +endif diff --git a/build/Android.oattest.mk b/build/Android.oattest.mk new file mode 100644 index 0000000000000000000000000000000000000000..034e47544641e428d0c32be2505b33b6b629f8ba --- /dev/null +++ b/build/Android.oattest.mk @@ -0,0 +1,101 @@ +# +# Copyright (C) 2011 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. +# + +######################################################################## + +ART_TEST_TARGET_DEX_FILES := +ART_TEST_HOST_DEX_FILES := + +# $(1): module prefix +# $(2): input test directory +# $(3): target output module path (default module path is used on host) +define build-art-test-dex + ifeq ($(ART_BUILD_TARGET),true) + include $(CLEAR_VARS) + LOCAL_MODULE := $(1)-$(2) + LOCAL_MODULE_TAGS := tests + LOCAL_SRC_FILES := $(call all-java-files-under, test/$(2)) + LOCAL_JAVA_LIBRARIES := $(TARGET_CORE_JARS) + LOCAL_NO_STANDARD_LIBRARIES := true + LOCAL_MODULE_PATH := $(3) + LOCAL_DEX_PREOPT_IMAGE := $(TARGET_CORE_IMG_OUT) + LOCAL_DEX_PREOPT := false + include $(BUILD_JAVA_LIBRARY) + ART_TEST_TARGET_DEX_FILES += $(3)/$$(LOCAL_MODULE).jar + endif + + ifeq ($(ART_BUILD_HOST),true) + include $(CLEAR_VARS) + LOCAL_MODULE := $(1)-$(2) + LOCAL_SRC_FILES := $(call all-java-files-under, test/$(2)) + LOCAL_JAVA_LIBRARIES := $(HOST_CORE_JARS) + LOCAL_NO_STANDARD_LIBRARIES := true + LOCAL_DEX_PREOPT_IMAGE := $(HOST_CORE_IMG_OUT) + LOCAL_BUILD_HOST_DEX := true + include $(BUILD_HOST_JAVA_LIBRARY) + ART_TEST_HOST_DEX_FILES += $$(LOCAL_MODULE_PATH)/$$(LOCAL_MODULE).jar + endif +endef +$(foreach dir,$(TEST_DEX_DIRECTORIES), $(eval $(call build-art-test-dex,art-test-dex,$(dir),$(ART_NATIVETEST_OUT)))) +$(foreach dir,$(TEST_OAT_DIRECTORIES), $(eval $(call build-art-test-dex,oat-test-dex,$(dir),$(ART_TEST_OUT)))) + +######################################################################## + +ART_TEST_TARGET_OAT_TARGETS := +ART_TEST_HOST_OAT_TARGETS := +ART_TEST_HOST_INTERPRETER_OAT_TARGETS := + +# $(1): directory +# $(2): arguments +define declare-test-art-oat-targets +.PHONY: test-art-target-oat-$(1) +test-art-target-oat-$(1): $(ART_TEST_OUT)/oat-test-dex-$(1).jar test-art-target-sync + adb shell touch $(ART_TEST_DIR)/test-art-target-oat-$(1) + adb shell rm $(ART_TEST_DIR)/test-art-target-oat-$(1) + adb shell sh -c "oatexecd -Ximage:$(ART_TEST_DIR)/core.art -classpath $(ART_TEST_DIR)/oat-test-dex-$(1).jar -Djava.library.path=$(ART_TEST_DIR) $(1) $(2) && touch $(ART_TEST_DIR)/test-art-target-oat-$(1)" + $(hide) (adb pull $(ART_TEST_DIR)/test-art-target-oat-$(1) /tmp/ && echo test-art-target-oat-$(1) PASSED) || (echo test-art-target-oat-$(1) FAILED && exit 1) + $(hide) rm /tmp/test-art-target-oat-$(1) + +$(HOST_OUT_JAVA_LIBRARIES)/oat-test-dex-$(1).jar.oat: $(HOST_OUT_JAVA_LIBRARIES)/oat-test-dex-$(1).jar $(HOST_CORE_IMG_OUT) | $(DEX2OAT) + $(DEX2OAT) --runtime-arg -Xms16m --runtime-arg -Xmx16m --boot-image=$(HOST_CORE_IMG_OUT) --dex-file=$$< --oat-file=$$@ --instruction-set=$(HOST_ARCH) --host --host-prefix="" --android-root=$(HOST_OUT) + +.PHONY: test-art-host-oat-$(1) +test-art-host-oat-$(1): $(HOST_OUT_JAVA_LIBRARIES)/oat-test-dex-$(1).jar.oat test-art-host-dependencies + mkdir -p /tmp/android-data/test-art-host-oat-$(1) + ANDROID_DATA=/tmp/android-data/test-art-host-oat-$(1) \ + ANDROID_ROOT=$(HOST_OUT) \ + LD_LIBRARY_PATH=$(HOST_OUT_SHARED_LIBRARIES) \ + oatexecd -Ximage:$(shell pwd)/$(HOST_CORE_IMG_OUT) -classpath $(HOST_OUT_JAVA_LIBRARIES)/oat-test-dex-$(1).jar -Djava.library.path=$(HOST_OUT_SHARED_LIBRARIES) $(1) $(2) \ + && echo test-art-host-oat-$(1) PASSED || (echo test-art-host-oat-$(1) FAILED && exit 1) + $(hide) rm -r /tmp/android-data/test-art-host-oat-$(1) + +.PHONY: test-art-host-interpreter-oat-$(1) +test-art-host-interpreter-oat-$(1): $(HOST_OUT_JAVA_LIBRARIES)/oat-test-dex-$(1).jar.oat test-art-host-dependencies + mkdir -p /tmp/android-data/test-art-host-interpreter-oat-$(1) + ANDROID_DATA=/tmp/android-data/test-art-host-interpreter-oat-$(1) \ + ANDROID_ROOT=$(HOST_OUT) \ + LD_LIBRARY_PATH=$(HOST_OUT_SHARED_LIBRARIES) \ + oatexecd -Ximage:$(shell pwd)/$(HOST_CORE_IMG_OUT) -Xint -classpath $(HOST_OUT_JAVA_LIBRARIES)/oat-test-dex-$(1).jar -Djava.library.path=$(HOST_OUT_SHARED_LIBRARIES) $(1) $(2) \ + && echo test-art-host-interpreter-oat-$(1) PASSED || (echo test-art-host-interpreter-oat-$(1) FAILED && exit 1) + $(hide) rm -r /tmp/android-data/test-art-host-interpreter-oat-$(1) + +ART_TEST_TARGET_OAT_TARGETS += test-art-target-oat-$(1) +ART_TEST_HOST_OAT_TARGETS += test-art-host-oat-$(1) +ART_TEST_HOST_INTERPRETER_OAT_TARGETS += test-art-host-interpreter-oat-$(1) +endef +$(foreach dir,$(TEST_OAT_DIRECTORIES), $(eval $(call declare-test-art-oat-targets,$(dir)))) + +######################################################################## diff --git a/jdwpspy/Android.mk b/jdwpspy/Android.mk new file mode 100644 index 0000000000000000000000000000000000000000..a6c2f64046fc395aa2b2d5e23664e118e2009c0b --- /dev/null +++ b/jdwpspy/Android.mk @@ -0,0 +1,15 @@ +# Copyright 2006 The Android Open Source Project + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + Main.cpp \ + Net.cpp + +LOCAL_C_INCLUDES += \ + art/src + +LOCAL_MODULE := jdwpspy + +include $(BUILD_HOST_EXECUTABLE) diff --git a/jdwpspy/Common.h b/jdwpspy/Common.h new file mode 100644 index 0000000000000000000000000000000000000000..0bd305643a7f9692b6d849e45d2cb1bb1e05e6c8 --- /dev/null +++ b/jdwpspy/Common.h @@ -0,0 +1,102 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * jdwpspy common stuff. + */ +#ifndef _JDWPSPY_COMMON +#define _JDWPSPY_COMMON + +#include +#include +#include + +typedef uint8_t u1; +typedef uint16_t u2; +typedef uint32_t u4; +typedef uint64_t u8; + +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) + +#ifndef _JDWP_MISC_INLINE +# define INLINE extern inline +#else +# define INLINE +#endif + +/* + * Get 1 byte. (Included to make the code more legible.) + */ +INLINE u1 get1(unsigned const char* pSrc) +{ + return *pSrc; +} + +/* + * Get 2 big-endian bytes. + */ +INLINE u2 get2BE(unsigned char const* pSrc) +{ + u2 result; + + result = *pSrc++ << 8; + result |= *pSrc++; + + return result; +} + +/* + * Get 4 big-endian bytes. + */ +INLINE u4 get4BE(unsigned char const* pSrc) +{ + u4 result; + + result = *pSrc++ << 24; + result |= *pSrc++ << 16; + result |= *pSrc++ << 8; + result |= *pSrc++; + + return result; +} + +/* + * Get 8 big-endian bytes. + */ +INLINE u8 get8BE(unsigned char const* pSrc) +{ + u8 result; + + result = (u8) *pSrc++ << 56; + result |= (u8) *pSrc++ << 48; + result |= (u8) *pSrc++ << 40; + result |= (u8) *pSrc++ << 32; + result |= (u8) *pSrc++ << 24; + result |= (u8) *pSrc++ << 16; + result |= (u8) *pSrc++ << 8; + result |= (u8) *pSrc++; + + return result; +} + + +/* + * Start here. + */ +int run(const char* connectHost, int connectPort, int listenPort); + +/* + * Print a hex dump to the specified file pointer. + * + * "local" mode prints a hex dump starting from offset 0 (roughly equivalent + * to "xxd -g1"). + * + * "mem" mode shows the actual memory address, and will offset the start + * so that the low nibble of the address is always zero. + */ +enum HexDumpMode { kHexDumpLocal, kHexDumpMem }; +void printHexDump(const void* vaddr, size_t length); +void printHexDump2(const void* vaddr, size_t length, const char* prefix); +void printHexDumpEx(FILE* fp, const void* vaddr, size_t length, + HexDumpMode mode, const char* prefix); + +#endif /*_JDWPSPY_COMMON*/ diff --git a/jdwpspy/Main.cpp b/jdwpspy/Main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0f68d52c38ddc7f6e4de4491b350c8b0603a7bfa --- /dev/null +++ b/jdwpspy/Main.cpp @@ -0,0 +1,139 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * JDWP spy. + */ +#define _JDWP_MISC_INLINE +#include "Common.h" +#include +#include +#include +#include +#include + +static const char gHexDigit[] = "0123456789abcdef"; + +/* + * Print a hex dump. Just hands control off to the fancy version. + */ +void printHexDump(const void* vaddr, size_t length) +{ + printHexDumpEx(stdout, vaddr, length, kHexDumpLocal, ""); +} +void printHexDump2(const void* vaddr, size_t length, const char* prefix) +{ + printHexDumpEx(stdout, vaddr, length, kHexDumpLocal, prefix); +} + +/* + * Print a hex dump in this format: + * +01234567: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef\n + */ +void printHexDumpEx(FILE* fp, const void* vaddr, size_t length, + HexDumpMode mode, const char* prefix) +{ + const unsigned char* addr = reinterpret_cast(vaddr); + char out[77]; /* exact fit */ + unsigned int offset; /* offset to show while printing */ + char* hex; + char* asc; + int gap; + + if (mode == kHexDumpLocal) + offset = 0; + else + offset = (int) addr; + + memset(out, ' ', sizeof(out)-1); + out[8] = ':'; + out[sizeof(out)-2] = '\n'; + out[sizeof(out)-1] = '\0'; + + gap = (int) offset & 0x0f; + while (length) { + unsigned int lineOffset = offset & ~0x0f; + char* hex = out; + char* asc = out + 59; + + for (int i = 0; i < 8; i++) { + *hex++ = gHexDigit[lineOffset >> 28]; + lineOffset <<= 4; + } + hex++; + hex++; + + int count = ((int)length > 16-gap) ? 16-gap : (int) length; /* cap length */ + assert(count != 0); + assert(count+gap <= 16); + + if (gap) { + /* only on first line */ + hex += gap * 3; + asc += gap; + } + + int i; + for (i = gap ; i < count+gap; i++) { + *hex++ = gHexDigit[*addr >> 4]; + *hex++ = gHexDigit[*addr & 0x0f]; + hex++; + if (isprint(*addr)) + *asc++ = *addr; + else + *asc++ = '.'; + addr++; + } + for ( ; i < 16; i++) { + /* erase extra stuff; only happens on last line */ + *hex++ = ' '; + *hex++ = ' '; + hex++; + *asc++ = ' '; + } + + fprintf(fp, "%s%s", prefix, out); + + gap = 0; + length -= count; + offset += count; + } +} + + +/* + * Explain it. + */ +static void usage(const char* progName) +{ + fprintf(stderr, "Usage: %s VM-port [debugger-listen-port]\n\n", progName); + fprintf(stderr, +"When a debugger connects to the debugger-listen-port, jdwpspy will connect\n"); + fprintf(stderr, "to the VM on the VM-port.\n"); +} + +/* + * Parse args. + */ +int main(int argc, char* argv[]) +{ + if (argc < 2 || argc > 3) { + usage("jdwpspy"); + return 2; + } + + setvbuf(stdout, NULL, _IONBF, 0); + + /* may want this to be host:port */ + int connectPort = atoi(argv[1]); + + int listenPort; + if (argc > 2) + listenPort = atoi(argv[2]); + else + listenPort = connectPort + 1; + + int cc = run("localhost", connectPort, listenPort); + + return (cc != 0); +} diff --git a/jdwpspy/Net.cpp b/jdwpspy/Net.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5c789d81ab6f30b5641ab4a2f4d745451f2d0f8b --- /dev/null +++ b/jdwpspy/Net.cpp @@ -0,0 +1,751 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * JDWP spy. This is a rearranged version of the JDWP code from the VM. + */ +#include "Common.h" +#include "jdwp/jdwp_constants.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define kInputBufferSize (256*1024) + +#define kMagicHandshakeLen 14 /* "JDWP-Handshake" */ +#define kJDWPHeaderLen 11 +#define kJDWPFlagReply 0x80 + + +/* + * Information about the remote end. + */ +struct Peer { + char label[2]; /* 'D' or 'V' */ + + int sock; + unsigned char inputBuffer[kInputBufferSize]; + int inputCount; + + bool awaitingHandshake; /* waiting for "JDWP-Handshake" */ +}; + + +/* + * Network state. + */ +struct NetState { + /* listen here for connection from debugger */ + int listenSock; + + /* connect here to contact VM */ + in_addr vmAddr; + uint16_t vmPort; + + Peer dbg; + Peer vm; +}; + +/* + * Function names. + */ +struct JdwpHandlerMap { + u1 cmdSet; + u1 cmd; + const char* descr; +}; + +/* + * Map commands to names. + * + * Command sets 0-63 are incoming requests, 64-127 are outbound requests, + * and 128-256 are vendor-defined. + */ +static const JdwpHandlerMap gHandlerMap[] = { + /* VirtualMachine command set (1) */ + { 1, 1, "VirtualMachine.Version" }, + { 1, 2, "VirtualMachine.ClassesBySignature" }, + { 1, 3, "VirtualMachine.AllClasses" }, + { 1, 4, "VirtualMachine.AllThreads" }, + { 1, 5, "VirtualMachine.TopLevelThreadGroups" }, + { 1, 6, "VirtualMachine.Dispose" }, + { 1, 7, "VirtualMachine.IDSizes" }, + { 1, 8, "VirtualMachine.Suspend" }, + { 1, 9, "VirtualMachine.Resume" }, + { 1, 10, "VirtualMachine.Exit" }, + { 1, 11, "VirtualMachine.CreateString" }, + { 1, 12, "VirtualMachine.Capabilities" }, + { 1, 13, "VirtualMachine.ClassPaths" }, + { 1, 14, "VirtualMachine.DisposeObjects" }, + { 1, 15, "VirtualMachine.HoldEvents" }, + { 1, 16, "VirtualMachine.ReleaseEvents" }, + { 1, 17, "VirtualMachine.CapabilitiesNew" }, + { 1, 18, "VirtualMachine.RedefineClasses" }, + { 1, 19, "VirtualMachine.SetDefaultStratum" }, + { 1, 20, "VirtualMachine.AllClassesWithGeneric"}, + { 1, 21, "VirtualMachine.InstanceCounts"}, + + /* ReferenceType command set (2) */ + { 2, 1, "ReferenceType.Signature" }, + { 2, 2, "ReferenceType.ClassLoader" }, + { 2, 3, "ReferenceType.Modifiers" }, + { 2, 4, "ReferenceType.Fields" }, + { 2, 5, "ReferenceType.Methods" }, + { 2, 6, "ReferenceType.GetValues" }, + { 2, 7, "ReferenceType.SourceFile" }, + { 2, 8, "ReferenceType.NestedTypes" }, + { 2, 9, "ReferenceType.Status" }, + { 2, 10, "ReferenceType.Interfaces" }, + { 2, 11, "ReferenceType.ClassObject" }, + { 2, 12, "ReferenceType.SourceDebugExtension" }, + { 2, 13, "ReferenceType.SignatureWithGeneric" }, + { 2, 14, "ReferenceType.FieldsWithGeneric" }, + { 2, 15, "ReferenceType.MethodsWithGeneric" }, + { 2, 16, "ReferenceType.Instances" }, + { 2, 17, "ReferenceType.ClassFileVersion" }, + { 2, 18, "ReferenceType.ConstantPool" }, + + /* ClassType command set (3) */ + { 3, 1, "ClassType.Superclass" }, + { 3, 2, "ClassType.SetValues" }, + { 3, 3, "ClassType.InvokeMethod" }, + { 3, 4, "ClassType.NewInstance" }, + + /* ArrayType command set (4) */ + { 4, 1, "ArrayType.NewInstance" }, + + /* InterfaceType command set (5) */ + + /* Method command set (6) */ + { 6, 1, "Method.LineTable" }, + { 6, 2, "Method.VariableTable" }, + { 6, 3, "Method.Bytecodes" }, + { 6, 4, "Method.IsObsolete" }, + { 6, 5, "Method.VariableTableWithGeneric" }, + + /* Field command set (8) */ + + /* ObjectReference command set (9) */ + { 9, 1, "ObjectReference.ReferenceType" }, + { 9, 2, "ObjectReference.GetValues" }, + { 9, 3, "ObjectReference.SetValues" }, + { 9, 4, "ObjectReference.UNUSED" }, + { 9, 5, "ObjectReference.MonitorInfo" }, + { 9, 6, "ObjectReference.InvokeMethod" }, + { 9, 7, "ObjectReference.DisableCollection" }, + { 9, 8, "ObjectReference.EnableCollection" }, + { 9, 9, "ObjectReference.IsCollected" }, + { 9, 10, "ObjectReference.ReferringObjects" }, + + /* StringReference command set (10) */ + { 10, 1, "StringReference.Value" }, + + /* ThreadReference command set (11) */ + { 11, 1, "ThreadReference.Name" }, + { 11, 2, "ThreadReference.Suspend" }, + { 11, 3, "ThreadReference.Resume" }, + { 11, 4, "ThreadReference.Status" }, + { 11, 5, "ThreadReference.ThreadGroup" }, + { 11, 6, "ThreadReference.Frames" }, + { 11, 7, "ThreadReference.FrameCount" }, + { 11, 8, "ThreadReference.OwnedMonitors" }, + { 11, 9, "ThreadReference.CurrentContendedMonitor" }, + { 11, 10, "ThreadReference.Stop" }, + { 11, 11, "ThreadReference.Interrupt" }, + { 11, 12, "ThreadReference.SuspendCount" }, + { 11, 13, "ThreadReference.OwnedMonitorsStackDepthInfo" }, + { 11, 14, "ThreadReference.ForceEarlyReturn" }, + + /* ThreadGroupReference command set (12) */ + { 12, 1, "ThreadGroupReference.Name" }, + { 12, 2, "ThreadGroupReference.Parent" }, + { 12, 3, "ThreadGroupReference.Children" }, + + /* ArrayReference command set (13) */ + { 13, 1, "ArrayReference.Length" }, + { 13, 2, "ArrayReference.GetValues" }, + { 13, 3, "ArrayReference.SetValues" }, + + /* ClassLoaderReference command set (14) */ + { 14, 1, "ArrayReference.VisibleClasses" }, + + /* EventRequest command set (15) */ + { 15, 1, "EventRequest.Set" }, + { 15, 2, "EventRequest.Clear" }, + { 15, 3, "EventRequest.ClearAllBreakpoints" }, + + /* StackFrame command set (16) */ + { 16, 1, "StackFrame.GetValues" }, + { 16, 2, "StackFrame.SetValues" }, + { 16, 3, "StackFrame.ThisObject" }, + { 16, 4, "StackFrame.PopFrames" }, + + /* ClassObjectReference command set (17) */ + { 17, 1, "ClassObjectReference.ReflectedType" }, + + /* Event command set (64) */ + { 64, 100, "Event.Composite" }, + + /* DDMS */ + { 199, 1, "DDMS.Chunk" }, +}; + +/* + * Look up a command's name. + */ +static const char* getCommandName(int cmdSet, int cmd) +{ + for (int i = 0; i < (int) NELEM(gHandlerMap); i++) { + if (gHandlerMap[i].cmdSet == cmdSet && + gHandlerMap[i].cmd == cmd) + { + return gHandlerMap[i].descr; + } + } + + return "?UNKNOWN?"; +} + + +void jdwpNetFree(NetState* netState); /* fwd */ + +/* + * Allocate state structure and bind to the listen port. + * + * Returns 0 on success. + */ +NetState* jdwpNetStartup(uint16_t listenPort, const char* connectHost, uint16_t connectPort) { + NetState* netState = new NetState; + memset(netState, 0, sizeof(*netState)); + netState->listenSock = -1; + netState->dbg.sock = netState->vm.sock = -1; + + strcpy(netState->dbg.label, "D"); + strcpy(netState->vm.label, "V"); + + /* + * Set up a socket to listen for connections from the debugger. + */ + + netState->listenSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (netState->listenSock < 0) { + fprintf(stderr, "Socket create failed: %s\n", strerror(errno)); + goto fail; + } + + /* allow immediate re-use if we die */ + { + int one = 1; + if (setsockopt(netState->listenSock, SOL_SOCKET, SO_REUSEADDR, &one, + sizeof(one)) < 0) + { + fprintf(stderr, "setsockopt(SO_REUSEADDR) failed: %s\n", + strerror(errno)); + goto fail; + } + } + + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(listenPort); + addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(netState->listenSock, (sockaddr*) &addr, sizeof(addr)) != 0) + { + fprintf(stderr, "attempt to bind to port %u failed: %s\n", + listenPort, strerror(errno)); + goto fail; + } + + fprintf(stderr, "+++ bound to port %u\n", listenPort); + + if (listen(netState->listenSock, 5) != 0) { + fprintf(stderr, "Listen failed: %s\n", strerror(errno)); + goto fail; + } + + /* + * Do the hostname lookup for the VM. + */ + hostent* pHost; + + pHost = gethostbyname(connectHost); + if (pHost == NULL) { + fprintf(stderr, "Name lookup of '%s' failed: %s\n", + connectHost, strerror(h_errno)); + goto fail; + } + + netState->vmAddr = *((in_addr*) pHost->h_addr_list[0]); + netState->vmPort = connectPort; + + fprintf(stderr, "+++ connect host resolved to %s\n", + inet_ntoa(netState->vmAddr)); + + return netState; + +fail: + jdwpNetFree(netState); + return NULL; +} + +/* + * Shut down JDWP listener. Don't free state. + * + * Note that "netState" may be partially initialized if "startup" failed. + */ +void jdwpNetShutdown(NetState* netState) +{ + int listenSock = netState->listenSock; + int dbgSock = netState->dbg.sock; + int vmSock = netState->vm.sock; + + /* clear these out so it doesn't wake up and try to reuse them */ + /* (important when multi-threaded) */ + netState->listenSock = netState->dbg.sock = netState->vm.sock = -1; + + if (listenSock >= 0) { + shutdown(listenSock, SHUT_RDWR); + close(listenSock); + } + if (dbgSock >= 0) { + shutdown(dbgSock, SHUT_RDWR); + close(dbgSock); + } + if (vmSock >= 0) { + shutdown(vmSock, SHUT_RDWR); + close(vmSock); + } +} + +/* + * Shut down JDWP listener and free its state. + */ +void jdwpNetFree(NetState* netState) +{ + if (netState == NULL) + return; + + jdwpNetShutdown(netState); + delete netState; +} + +/* + * Disable the TCP Nagle algorithm, which delays transmission of outbound + * packets until the previous transmissions have been acked. JDWP does a + * lot of back-and-forth with small packets, so this may help. + */ +static int setNoDelay(int fd) +{ + int cc, on = 1; + + cc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); + assert(cc == 0); + return cc; +} + +/* + * Accept a connection. This will block waiting for somebody to show up. + */ +bool jdwpAcceptConnection(NetState* netState) +{ + sockaddr_in addr; + socklen_t addrlen; + int sock; + + if (netState->listenSock < 0) + return false; /* you're not listening! */ + + assert(netState->dbg.sock < 0); /* must not already be talking */ + + addrlen = sizeof(addr); + do { + sock = accept(netState->listenSock, (sockaddr*) &addr, &addrlen); + if (sock < 0 && errno != EINTR) { + fprintf(stderr, "accept failed: %s\n", strerror(errno)); + return false; + } + } while (sock < 0); + + fprintf(stderr, "+++ accepted connection from %s:%u\n", + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + + netState->dbg.sock = sock; + netState->dbg.awaitingHandshake = true; + netState->dbg.inputCount = 0; + + setNoDelay(sock); + + return true; +} + +/* + * Close the connections to the debugger and VM. + * + * Reset the state so we're ready to receive a new connection. + */ +void jdwpCloseConnection(NetState* netState) +{ + if (netState->dbg.sock >= 0) { + fprintf(stderr, "+++ closing connection to debugger\n"); + close(netState->dbg.sock); + netState->dbg.sock = -1; + } + if (netState->vm.sock >= 0) { + fprintf(stderr, "+++ closing connection to vm\n"); + close(netState->vm.sock); + netState->vm.sock = -1; + } +} + +/* + * Figure out if we have a full packet in the buffer. + */ +static bool haveFullPacket(Peer* pPeer) +{ + long length; + + if (pPeer->awaitingHandshake) + return (pPeer->inputCount >= kMagicHandshakeLen); + + if (pPeer->inputCount < 4) + return false; + + length = get4BE(pPeer->inputBuffer); + return (pPeer->inputCount >= length); +} + +/* + * Consume bytes from the buffer. + * + * This would be more efficient with a circular buffer. However, we're + * usually only going to find one packet, which is trivial to handle. + */ +static void consumeBytes(Peer* pPeer, int count) +{ + assert(count > 0); + assert(count <= pPeer->inputCount); + + if (count == pPeer->inputCount) { + pPeer->inputCount = 0; + return; + } + + memmove(pPeer->inputBuffer, pPeer->inputBuffer + count, + pPeer->inputCount - count); + pPeer->inputCount -= count; +} + +/* + * Get the current time. + */ +static void getCurrentTime(int* pMin, int* pSec) +{ + time_t now; + tm* ptm; + + now = time(NULL); + ptm = localtime(&now); + *pMin = ptm->tm_min; + *pSec = ptm->tm_sec; +} + +/* + * Dump the contents of a packet to stdout. + */ +static void dumpPacket(const unsigned char* packetBuf, const char* srcName, + const char* dstName) +{ + const unsigned char* buf = packetBuf; + char prefix[3]; + u4 length, id; + u1 flags, cmdSet=0, cmd=0; + art::JDWP::JdwpError error = art::JDWP::ERR_NONE; + bool reply; + int dataLen; + + length = get4BE(buf+0); + id = get4BE(buf+4); + flags = get1(buf+8); + if ((flags & kJDWPFlagReply) != 0) { + reply = true; + error = static_cast(get2BE(buf+9)); + } else { + reply = false; + cmdSet = get1(buf+9); + cmd = get1(buf+10); + } + + buf += kJDWPHeaderLen; + dataLen = length - (buf - packetBuf); + + if (!reply) { + prefix[0] = srcName[0]; + prefix[1] = '>'; + } else { + prefix[0] = dstName[0]; + prefix[1] = '<'; + } + prefix[2] = '\0'; + + int min, sec; + getCurrentTime(&min, &sec); + + if (!reply) { + printf("%s REQUEST dataLen=%-5u id=0x%08x flags=0x%02x cmd=%d/%d [%02d:%02d]\n", + prefix, dataLen, id, flags, cmdSet, cmd, min, sec); + printf("%s --> %s\n", prefix, getCommandName(cmdSet, cmd)); + } else { + std::ostringstream ss; + ss << "TODO"; // get access to the operator<<, or regenerate it for jdwpspy? + printf("%s REPLY dataLen=%-5u id=0x%08x flags=0x%02x err=%d (%s) [%02d:%02d]\n", + prefix, dataLen, id, flags, error, ss.str().c_str(), min,sec); + } + if (dataLen > 0) + printHexDump2(buf, dataLen, prefix); + printf("%s ----------\n", prefix); +} + +/* + * Handle a packet. Returns "false" if we encounter a connection-fatal error. + */ +static bool handlePacket(Peer* pDst, Peer* pSrc) +{ + const unsigned char* buf = pSrc->inputBuffer; + u4 length; + u1 flags; + int cc; + + length = get4BE(buf+0); + flags = get1(buf+9); + + assert((int) length <= pSrc->inputCount); + + dumpPacket(buf, pSrc->label, pDst->label); + + cc = write(pDst->sock, buf, length); + if (cc != (int) length) { + fprintf(stderr, "Failed sending packet: %s\n", strerror(errno)); + return false; + } + /*printf("*** wrote %d bytes from %c to %c\n", + cc, pSrc->label[0], pDst->label[0]);*/ + + consumeBytes(pSrc, length); + return true; +} + +/* + * Handle incoming data. If we have a full packet in the buffer, process it. + */ +static bool handleIncoming(Peer* pWritePeer, Peer* pReadPeer) +{ + if (haveFullPacket(pReadPeer)) { + if (pReadPeer->awaitingHandshake) { + printf("Handshake [%c]: %.14s\n", + pReadPeer->label[0], pReadPeer->inputBuffer); + if (write(pWritePeer->sock, pReadPeer->inputBuffer, + kMagicHandshakeLen) != kMagicHandshakeLen) + { + fprintf(stderr, + "+++ [%c] handshake write failed\n", pReadPeer->label[0]); + goto fail; + } + consumeBytes(pReadPeer, kMagicHandshakeLen); + pReadPeer->awaitingHandshake = false; + } else { + if (!handlePacket(pWritePeer, pReadPeer)) + goto fail; + } + } else { + /*printf("*** %c not full yet\n", pReadPeer->label[0]);*/ + } + + return true; + +fail: + return false; +} + +/* + * Process incoming data. If no data is available, this will block until + * some arrives. + * + * Returns "false" on error (indicating that the connection has been severed). + */ +bool jdwpProcessIncoming(NetState* netState) +{ + int cc; + + assert(netState->dbg.sock >= 0); + assert(netState->vm.sock >= 0); + + while (!haveFullPacket(&netState->dbg) && !haveFullPacket(&netState->vm)) { + /* read some more */ + int highFd; + fd_set readfds; + + highFd = (netState->dbg.sock > netState->vm.sock) ? + netState->dbg.sock+1 : netState->vm.sock+1; + FD_ZERO(&readfds); + FD_SET(netState->dbg.sock, &readfds); + FD_SET(netState->vm.sock, &readfds); + + errno = 0; + cc = select(highFd, &readfds, NULL, NULL, NULL); + if (cc < 0) { + if (errno == EINTR) { + fprintf(stderr, "+++ EINTR on select\n"); + continue; + } + fprintf(stderr, "+++ select failed: %s\n", strerror(errno)); + goto fail; + } + + if (FD_ISSET(netState->dbg.sock, &readfds)) { + cc = read(netState->dbg.sock, + netState->dbg.inputBuffer + netState->dbg.inputCount, + sizeof(netState->dbg.inputBuffer) - netState->dbg.inputCount); + if (cc < 0) { + if (errno == EINTR) { + fprintf(stderr, "+++ EINTR on read\n"); + continue; + } + fprintf(stderr, "+++ dbg read failed: %s\n", strerror(errno)); + goto fail; + } + if (cc == 0) { + if (sizeof(netState->dbg.inputBuffer) == + netState->dbg.inputCount) + fprintf(stderr, "+++ debugger sent huge message\n"); + else + fprintf(stderr, "+++ debugger disconnected\n"); + goto fail; + } + + /*printf("*** %d bytes from dbg\n", cc);*/ + netState->dbg.inputCount += cc; + } + + if (FD_ISSET(netState->vm.sock, &readfds)) { + cc = read(netState->vm.sock, + netState->vm.inputBuffer + netState->vm.inputCount, + sizeof(netState->vm.inputBuffer) - netState->vm.inputCount); + if (cc < 0) { + if (errno == EINTR) { + fprintf(stderr, "+++ EINTR on read\n"); + continue; + } + fprintf(stderr, "+++ vm read failed: %s\n", strerror(errno)); + goto fail; + } + if (cc == 0) { + if (sizeof(netState->vm.inputBuffer) == + netState->vm.inputCount) + fprintf(stderr, "+++ vm sent huge message\n"); + else + fprintf(stderr, "+++ vm disconnected\n"); + goto fail; + } + + /*printf("*** %d bytes from vm\n", cc);*/ + netState->vm.inputCount += cc; + } + } + + if (!handleIncoming(&netState->dbg, &netState->vm)) + goto fail; + if (!handleIncoming(&netState->vm, &netState->dbg)) + goto fail; + + return true; + +fail: + jdwpCloseConnection(netState); + return false; +} + +/* + * Connect to the VM. + */ +bool jdwpConnectToVm(NetState* netState) +{ + sockaddr_in addr; + int sock = -1; + + sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock < 0) { + fprintf(stderr, "Socket create failed: %s\n", strerror(errno)); + goto fail; + } + + addr.sin_family = AF_INET; + addr.sin_addr = netState->vmAddr; + addr.sin_port = htons(netState->vmPort); + if (connect(sock, (struct sockaddr*) &addr, sizeof(addr)) != 0) { + fprintf(stderr, "Connection to %s:%u failed: %s\n", + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), strerror(errno)); + goto fail; + } + fprintf(stderr, "+++ connected to VM %s:%u\n", + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + + netState->vm.sock = sock; + netState->vm.awaitingHandshake = true; + netState->vm.inputCount = 0; + + setNoDelay(netState->vm.sock); + return true; + +fail: + if (sock >= 0) + close(sock); + return false; +} + +/* + * Establish network connections and start things running. + * + * We wait for a new connection from the debugger. When one arrives we + * open a connection to the VM. If one side or the other goes away, we + * drop both ends and go back to listening. + */ +int run(const char* connectHost, int connectPort, int listenPort) +{ + NetState* state; + + state = jdwpNetStartup(listenPort, connectHost, connectPort); + if (state == NULL) + return -1; + + while (true) { + if (!jdwpAcceptConnection(state)) + break; + + if (jdwpConnectToVm(state)) { + while (true) { + if (!jdwpProcessIncoming(state)) + break; + } + } + + jdwpCloseConnection(state); + } + + jdwpNetFree(state); + + return 0; +} diff --git a/src/asm_support.h b/src/asm_support.h new file mode 100644 index 0000000000000000000000000000000000000000..8ea4adfcf28cf34a6a9043f7eef7ffb7772b6162 --- /dev/null +++ b/src/asm_support.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 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_SRC_ASM_SUPPORT_H_ +#define ART_SRC_ASM_SUPPORT_H_ + +// Value loaded into rSUSPEND for quick. When this value is counted down to zero we do a suspend +// check. +#define SUSPEND_CHECK_INTERVAL (1000) + +// Offsets within java.lang.String. +#define STRING_VALUE_OFFSET 8 +#define STRING_COUNT_OFFSET 12 +#define STRING_OFFSET_OFFSET 20 +#define STRING_DATA_OFFSET 12 + +// Offset of field Method::entry_point_from_compiled_code_ +#define METHOD_CODE_OFFSET 40 + +#if defined(__arm__) +// Register holding suspend check count down. +#define rSUSPEND r4 +// Register holding Thread::Current(). +#define rSELF r9 +// Offset of field Thread::suspend_count_ verified in InitCpu +#define THREAD_FLAGS_OFFSET 0 +// Offset of field Thread::exception_ verified in InitCpu +#define THREAD_EXCEPTION_OFFSET 12 +#elif defined(__mips__) +// Register holding suspend check count down. +#define rSUSPEND $s0 +// Register holding Thread::Current(). +#define rSELF $s1 +// Offset of field Thread::suspend_count_ verified in InitCpu +#define THREAD_FLAGS_OFFSET 0 +// Offset of field Thread::exception_ verified in InitCpu +#define THREAD_EXCEPTION_OFFSET 12 +#elif defined(__i386__) +// Offset of field Thread::self_ verified in InitCpu +#define THREAD_SELF_OFFSET 40 +// Offset of field Thread::exception_ verified in InitCpu +#define THREAD_EXCEPTION_OFFSET 12 +#endif + +#endif // ART_SRC_ASM_SUPPORT_H_ diff --git a/src/atomic.cc b/src/atomic.cc new file mode 100644 index 0000000000000000000000000000000000000000..4efb06187b2a64f3e1554f1f79efc9a8dc7336c4 --- /dev/null +++ b/src/atomic.cc @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "atomic.h" + +#define NEED_SWAP_MUTEXES !defined(__arm__) && !defined(__i386__) + +#if NEED_SWAP_MUTEXES +#include +#include "base/mutex.h" +#include "base/stl_util.h" +#include "base/stringprintf.h" +#include "thread.h" +#endif + +namespace art { + +#if NEED_SWAP_MUTEXES +// We stripe across a bunch of different mutexes to reduce contention. +static const size_t kSwapMutexCount = 32; +static std::vector* gSwapMutexes; + +static Mutex& GetSwapMutex(const volatile int64_t* addr) { + return *(*gSwapMutexes)[((unsigned)(void*)(addr) >> 3U) % kSwapMutexCount]; +} +#endif + +void QuasiAtomic::Startup() { +#if NEED_SWAP_MUTEXES + gSwapMutexes = new std::vector; + for (size_t i = 0; i < kSwapMutexCount; ++i) { + gSwapMutexes->push_back(new Mutex("QuasiAtomic stripe")); + } +#endif +} + +void QuasiAtomic::Shutdown() { +#if NEED_SWAP_MUTEXES + STLDeleteElements(gSwapMutexes); + delete gSwapMutexes; +#endif +} + +int64_t QuasiAtomic::Read64(volatile const int64_t* addr) { + int64_t value; +#if NEED_SWAP_MUTEXES + MutexLock mu(Thread::Current(), GetSwapMutex(addr)); + value = *addr; +#elif defined(__arm__) + // Exclusive loads are defined not to tear, clearing the exclusive state isn't necessary. If we + // have LPAE (such as Cortex-A15) then ldrd would suffice. + __asm__ __volatile__("@ QuasiAtomic::Read64\n" + "ldrexd %0, %H0, [%1]" + : "=&r" (value) + : "r" (addr)); +#elif defined(__i386__) + __asm__ __volatile__( + "movq %1, %0\n" + : "=x" (value) + : "m" (*addr)); +#else +#error Unexpected architecture +#endif + return value; +} + +void QuasiAtomic::Write64(volatile int64_t* addr, int64_t value) { +#if NEED_SWAP_MUTEXES + MutexLock mu(Thread::Current(), GetSwapMutex(addr)); + *addr = value; +#elif defined(__arm__) + // The write is done as a swap so that the cache-line is in the exclusive state for the store. If + // we know that ARM architecture has LPAE (such as Cortex-A15) this isn't necessary and strd will + // suffice. + int64_t prev; + int status; + do { + __asm__ __volatile__("@ QuasiAtomic::Write64\n" + "ldrexd %0, %H0, [%3]\n" + "strexd %1, %4, %H4, [%3]" + : "=&r" (prev), "=&r" (status), "+m"(*addr) + : "r" (addr), "r" (value) + : "cc"); + } while (__builtin_expect(status != 0, 0)); +#elif defined(__i386__) + __asm__ __volatile__( + "movq %1, %0" + : "=m" (*addr) + : "x" (value)); +#else +#error Unexpected architecture +#endif +} + + +bool QuasiAtomic::Cas64(int64_t old_value, int64_t new_value, volatile int64_t* addr) { +#if NEED_SWAP_MUTEXES + MutexLock mu(Thread::Current(), GetSwapMutex(addr)); + if (*addr == old_value) { + *addr = new_value; + return true; + } + return false; +#elif defined(__arm__) + int64_t prev; + int status; + do { + __asm__ __volatile__("@ QuasiAtomic::Cas64\n" + "ldrexd %0, %H0, [%3]\n" + "mov %1, #0\n" + "teq %0, %4\n" + "teqeq %H0, %H4\n" + "strexdeq %1, %5, %H5, [%3]" + : "=&r" (prev), "=&r" (status), "+m"(*addr) + : "r" (addr), "Ir" (old_value), "r" (new_value) + : "cc"); + } while (__builtin_expect(status != 0, 0)); + return prev == old_value; +#elif defined(__i386__) + // cmpxchg8b implicitly uses %ebx which is also the PIC register. + int8_t status; + __asm__ __volatile__ ( + "pushl %%ebx\n" + "movl (%3), %%ebx\n" + "movl 4(%3), %%ecx\n" + "lock cmpxchg8b %1\n" + "sete %0\n" + "popl %%ebx" + : "=R" (status), "+m" (*addr) + : "A"(old_value), "D" (&new_value) + : "%ecx" + ); + return status != 0; +#else +#error Unexpected architecture +#endif +} + +bool QuasiAtomic::LongAtomicsUseMutexes() { +#if NEED_SWAP_MUTEXES + return true; +#else + return false; +#endif +} + +} // namespace art diff --git a/src/atomic.h b/src/atomic.h new file mode 100644 index 0000000000000000000000000000000000000000..d340dc5474e07ab3d6f774b32a42c535314c1dba --- /dev/null +++ b/src/atomic.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_SRC_ATOMIC_H_ +#define ART_SRC_ATOMIC_H_ + +#include + +#include "base/macros.h" + +namespace art { + +// NOTE: Two "quasiatomic" operations on the exact same memory address +// are guaranteed to operate atomically with respect to each other, +// but no guarantees are made about quasiatomic operations mixed with +// non-quasiatomic operations on the same address, nor about +// quasiatomic operations that are performed on partially-overlapping +// memory. +class QuasiAtomic { + public: + static void Startup(); + + static void Shutdown(); + + // Reads the 64-bit value at "addr" without tearing. + static int64_t Read64(volatile const int64_t* addr); + + // Writes to the 64-bit value at "addr" without tearing. + static void Write64(volatile int64_t* addr, int64_t val); + + // Atomically compare the value at "addr" to "old_value", if equal replace it with "new_value" + // and return true. Otherwise, don't swap, and return false. + static bool Cas64(int64_t old_value, int64_t new_value, volatile int64_t* addr); + + // Does the architecture provide reasonable atomic long operations or do we fall back on mutexes? + static bool LongAtomicsUseMutexes(); + + private: + DISALLOW_COPY_AND_ASSIGN(QuasiAtomic); +}; + +} // namespace art + +#endif // ART_SRC_ATOMIC_H_ diff --git a/src/atomic_integer.h b/src/atomic_integer.h new file mode 100644 index 0000000000000000000000000000000000000000..188f4c28b0d849d7f5a3491134474fe0758fcc53 --- /dev/null +++ b/src/atomic_integer.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_SRC_ATOMIC_INTEGER_H_ +#define ART_SRC_ATOMIC_INTEGER_H_ + +#include "cutils/atomic.h" +#include "cutils/atomic-inline.h" + +namespace art { + +class AtomicInteger { + public: + AtomicInteger() : value_(0) { } + + AtomicInteger(int32_t value) : value_(value) { } + + // Unsafe = operator for non atomic operations on the integer. + AtomicInteger& operator = (int32_t new_value) { + value_ = new_value; + return *this; + } + + operator int32_t () const { + return value_; + } + + int32_t get() const { + return value_; + } + + int32_t operator += (const int32_t value) { + return android_atomic_add(value, &value_); + } + + int32_t operator -= (const int32_t value) { + return android_atomic_add(-value, &value_); + } + + int32_t operator |= (const int32_t value) { + return android_atomic_or(value, &value_); + } + + int32_t operator &= (const int32_t value) { + return android_atomic_and(-value, &value_); + } + + int32_t operator ++ () { + return android_atomic_inc(&value_) + 1; + } + + int32_t operator -- () { + return android_atomic_dec(&value_) - 1; + } + + bool CompareAndSwap(int expected_value, int new_value) { + bool success = android_atomic_cas(expected_value, new_value, &value_) == 0; + return success; + } + private: + int32_t value_; +}; + +} + +#endif // ART_SRC_ATOMIC_INTEGER_H_ diff --git a/src/barrier.cc b/src/barrier.cc new file mode 100644 index 0000000000000000000000000000000000000000..250d468adb2b020d0519195fc03ca517d28736b1 --- /dev/null +++ b/src/barrier.cc @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "barrier.h" + +#include "base/mutex.h" +#include "thread.h" + +namespace art { + +Barrier::Barrier(int count) + : count_(count), + lock_("GC barrier lock"), + condition_("GC barrier condition", lock_) { +} + +void Barrier::Pass(Thread* self) { + MutexLock mu(self, lock_); + SetCountLocked(self, count_ - 1); +} + +void Barrier::Wait(Thread* self) { + Increment(self, -1); +} + +void Barrier::Init(Thread* self, int count) { + MutexLock mu(self, lock_); + SetCountLocked(self, count); +} + +void Barrier::Increment(Thread* self, int delta) { + MutexLock mu(self, lock_); + SetCountLocked(self, count_ + delta); + if (count_ != 0) { + condition_.Wait(self); + } +} + +void Barrier::SetCountLocked(Thread* self, int count) { + count_ = count; + if (count_ == 0) { + condition_.Broadcast(self); + } +} + +Barrier::~Barrier() { + CHECK(!count_) << "Attempted to destroy barrier with non zero count"; +} + +} diff --git a/src/barrier.h b/src/barrier.h new file mode 100644 index 0000000000000000000000000000000000000000..2b0429a7c2ac645188d9c936b93032127e4705f7 --- /dev/null +++ b/src/barrier.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_SRC_BARRIER_H_ +#define ART_SRC_BARRIER_H_ + +#include "base/mutex.h" +#include "locks.h" +#include "UniquePtr.h" + +namespace art { + +class Barrier { + public: + Barrier(int count); + virtual ~Barrier(); + + // Pass through the barrier, decrements the count but does not block. + void Pass(Thread* self); + + // Wait on the barrier, decrement the count. + void Wait(Thread* self); + + // Set the count to a new value, if the value is 0 then everyone waiting on the condition + // variable is resumed. + void Init(Thread* self, int count); + + // Increment the count by delta, wait on condition if count is non zero. + void Increment(Thread* self, int delta); + + private: + void SetCountLocked(Thread* self, int count) EXCLUSIVE_LOCKS_REQUIRED(lock_); + + // Counter, when this reaches 0 all people blocked on the barrier are signalled. + int count_ GUARDED_BY(lock_); + + Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; + ConditionVariable condition_ GUARDED_BY(lock_); +}; + +} // namespace art +#endif // ART_SRC_GC_BARRIER_H_ diff --git a/src/barrier_test.cc b/src/barrier_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..093ba3535abfa5629c1d6fb8879101b5e6bafb65 --- /dev/null +++ b/src/barrier_test.cc @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "barrier.h" + +#include + +#include "atomic_integer.h" +#include "common_test.h" +#include "mirror/object_array-inl.h" +#include "thread_pool.h" +#include "UniquePtr.h" + +namespace art { +class CheckWaitTask : public Task { + public: + CheckWaitTask(Barrier* barrier, AtomicInteger* count1, AtomicInteger* count2, + AtomicInteger* count3) + : barrier_(barrier), + count1_(count1), + count2_(count2), + count3_(count3) { + + } + + void Run(Thread* self) { + LOG(INFO) << "Before barrier 1 " << *self; + ++*count1_; + barrier_->Wait(self); + ++*count2_; + LOG(INFO) << "Before barrier 2 " << *self; + barrier_->Wait(self); + ++*count3_; + LOG(INFO) << "After barrier 2 " << *self; + } + + virtual void Finalize() { + delete this; + } + private: + Barrier* const barrier_; + AtomicInteger* const count1_; + AtomicInteger* const count2_; + AtomicInteger* const count3_; +}; + +class BarrierTest : public CommonTest { + public: + static int32_t num_threads; +}; + +int32_t BarrierTest::num_threads = 4; + +// Check that barrier wait and barrier increment work. +TEST_F(BarrierTest, CheckWait) { + Thread* self = Thread::Current(); + ThreadPool thread_pool(num_threads); + Barrier barrier(0); + AtomicInteger count1 = 0; + AtomicInteger count2 = 0; + AtomicInteger count3 = 0; + for (int32_t i = 0; i < num_threads; ++i) { + thread_pool.AddTask(self, new CheckWaitTask(&barrier, &count1, &count2, &count3)); + } + thread_pool.StartWorkers(self); + barrier.Increment(self, num_threads); + // At this point each thread should have passed through the barrier. The first count should be + // equal to num_threads. + EXPECT_EQ(num_threads, count1); + // Count 3 should still be zero since no thread should have gone past the second barrier. + EXPECT_EQ(0, count3); + // Now lets tell the threads to pass again. + barrier.Increment(self, num_threads); + // Count 2 should be equal to num_threads since each thread must have passed the second barrier + // at this point. + EXPECT_EQ(num_threads, count2); + // Wait for all the threads to finish. + thread_pool.Wait(self); + // All three counts should be equal to num_threads now. + EXPECT_EQ(count1, count2); + EXPECT_EQ(count2, count3); + EXPECT_EQ(num_threads, count3); +} + +class CheckPassTask : public Task { + public: + CheckPassTask(Barrier* barrier, AtomicInteger* count, size_t subtasks) + : barrier_(barrier), + count_(count), + subtasks_(subtasks) { + + } + + void Run(Thread* self) { + for (size_t i = 0; i < subtasks_; ++i) { + ++*count_; + // Pass through to next subtask. + barrier_->Pass(self); + } + } + + void Finalize() { + delete this; + } + private: + Barrier* const barrier_; + AtomicInteger* const count_; + const size_t subtasks_; +}; + +// Check that barrier pass through works. +TEST_F(BarrierTest, CheckPass) { + Thread* self = Thread::Current(); + ThreadPool thread_pool(num_threads); + Barrier barrier(0); + AtomicInteger count = 0; + const int32_t num_tasks = num_threads * 4; + const int32_t num_sub_tasks = 128; + for (int32_t i = 0; i < num_tasks; ++i) { + thread_pool.AddTask(self, new CheckPassTask(&barrier, &count, num_sub_tasks)); + } + thread_pool.StartWorkers(self); + const int32_t expected_total_tasks = num_sub_tasks * num_tasks; + // Wait for all the tasks to complete using the barrier. + barrier.Increment(self, expected_total_tasks); + // The total number of completed tasks should be equal to expected_total_tasks. + EXPECT_EQ(count, expected_total_tasks); +} + +} // namespace art diff --git a/src/base/casts.h b/src/base/casts.h new file mode 100644 index 0000000000000000000000000000000000000000..34c05af4e3334e13c4d782587e26e53f80f4a9ec --- /dev/null +++ b/src/base/casts.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2011 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_SRC_BASE_CASTS_H_ +#define ART_SRC_BASE_CASTS_H_ + +#include +#include +#include "base/macros.h" + +namespace art { + +// Use implicit_cast as a safe version of static_cast or const_cast +// for upcasting in the type hierarchy (i.e. casting a pointer to Foo +// to a pointer to SuperclassOfFoo or casting a pointer to Foo to +// a const pointer to Foo). +// When you use implicit_cast, the compiler checks that the cast is safe. +// Such explicit implicit_casts are necessary in surprisingly many +// situations where C++ demands an exact type match instead of an +// argument type convertable to a target type. +// +// The From type can be inferred, so the preferred syntax for using +// implicit_cast is the same as for static_cast etc.: +// +// implicit_cast(expr) +// +// implicit_cast would have been part of the C++ standard library, +// but the proposal was submitted too late. It will probably make +// its way into the language in the future. +template +inline To implicit_cast(From const &f) { + return f; +} + +// When you upcast (that is, cast a pointer from type Foo to type +// SuperclassOfFoo), it's fine to use implicit_cast<>, since upcasts +// always succeed. When you downcast (that is, cast a pointer from +// type Foo to type SubclassOfFoo), static_cast<> isn't safe, because +// how do you know the pointer is really of type SubclassOfFoo? It +// could be a bare Foo, or of type DifferentSubclassOfFoo. Thus, +// when you downcast, you should use this macro. In debug mode, we +// use dynamic_cast<> to double-check the downcast is legal (we die +// if it's not). In normal mode, we do the efficient static_cast<> +// instead. Thus, it's important to test in debug mode to make sure +// the cast is legal! +// This is the only place in the code we should use dynamic_cast<>. +// In particular, you SHOULDN'T be using dynamic_cast<> in order to +// do RTTI (eg code like this: +// if (dynamic_cast(foo)) HandleASubclass1Object(foo); +// if (dynamic_cast(foo)) HandleASubclass2Object(foo); +// You should design the code some other way not to need this. + +template // use like this: down_cast(foo); +inline To down_cast(From* f) { // so we only accept pointers + // Ensures that To is a sub-type of From *. This test is here only + // for compile-time type checking, and has no overhead in an + // optimized build at run-time, as it will be optimized away + // completely. + if (false) { + implicit_cast(0); + } + + // + // assert(f == NULL || dynamic_cast(f) != NULL); // RTTI: debug mode only! + return static_cast(f); +} + +template +inline Dest bit_cast(const Source& source) { + // Compile time assertion: sizeof(Dest) == sizeof(Source) + // A compile error here means your Dest and Source have different sizes. + COMPILE_ASSERT(sizeof(Dest) == sizeof(Source), verify_sizes_are_equal); + Dest dest; + memcpy(&dest, &source, sizeof(dest)); + return dest; +} + +} // namespace art + +#endif // ART_SRC_BASE_CASTS_H_ diff --git a/src/base/histogram-inl.h b/src/base/histogram-inl.h new file mode 100644 index 0000000000000000000000000000000000000000..9514209c111c7b5e994d2248bec6167f243016e9 --- /dev/null +++ b/src/base/histogram-inl.h @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2013 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 SRC_BASE_HISTOGRAM_INL_H_ +#define SRC_BASE_HISTOGRAM_INL_H_ + +#include "histogram.h" + +#include "utils.h" + +#include +#include +#include +#include + +namespace art { + +template inline void Histogram::AddValue(Value value) { + CHECK_GE(value, 0.0); + if (value >= max_) { + Value new_max = ((value + 1) / bucket_width_ + 1) * bucket_width_; + DCHECK_GT(new_max, max_); + GrowBuckets(new_max); + } + + BucketiseValue(value); + new_values_added_ = true; +} + +template +inline Histogram::Histogram(const std::string name) + : kAdjust(1000), + kBucketWidth(5), + kInitialBucketCount(10), + bucket_width_(kBucketWidth), + bucket_count_(kInitialBucketCount) { + name_ = name; + Reset(); +} + +template +inline void Histogram::GrowBuckets(Value new_max) { + while (max_ < new_max) { + max_ += bucket_width_; + ranges_.push_back(max_); + frequency_.push_back(0); + bucket_count_++; + } +} + +template inline size_t Histogram::FindBucket(Value val) { + // Since this is only a linear histogram, bucket index can be found simply with + // dividing the value by the bucket width. + DCHECK_GE(val, min_); + DCHECK_LE(val, max_); + size_t bucket_idx = static_cast((double)(val - min_) / bucket_width_); + DCHECK_GE(bucket_idx, 0ul); + DCHECK_LE(bucket_idx, bucket_count_); + return bucket_idx; +} + +template +inline void Histogram::BucketiseValue(Value value) { + CHECK_LT(value, max_); + sum_ += value; + sum_of_squares_ += value * value; + size_t bucket_idx = FindBucket(value); + sample_size_++; + if (value > max_value_added_) { + max_value_added_ = value; + } + if (value < min_value_added_) { + min_value_added_ = value; + } + frequency_[bucket_idx]++; +} + +template inline void Histogram::Initialize() { + DCHECK_GT(bucket_count_, 0ul); + size_t idx = 0; + for (; idx < bucket_count_; idx++) { + ranges_.push_back(min_ + static_cast(idx) * (bucket_width_)); + frequency_.push_back(0); + } + // Cumulative frequency and ranges has a length of 1 over frequency. + ranges_.push_back(min_ + idx * bucket_width_); + max_ = bucket_width_ * bucket_count_; +} + +template inline void Histogram::Reset() { + bucket_width_ = kBucketWidth; + bucket_count_ = kInitialBucketCount; + max_ = bucket_width_ * bucket_count_; + sum_of_squares_ = 0; + sample_size_ = 0; + min_ = 0; + sum_ = 0; + min_value_added_ = std::numeric_limits::max(); + max_value_added_ = std::numeric_limits::min(); + new_values_added_ = false; + ranges_.clear(); + frequency_.clear(); + cumulative_freq_.clear(); + cumulative_perc_.clear(); + Initialize(); +} + +template inline void Histogram::BuildRanges() { + for (size_t idx = 0; idx < bucket_count_; ++idx) { + ranges_.push_back(min_ + idx * bucket_width_); + } +} + +template inline double Histogram::Mean() const { + DCHECK_GT(sample_size_, 0ull); + return static_cast(sum_) / static_cast(sample_size_); +} + +template inline double Histogram::Variance() const { + DCHECK_GT(sample_size_, 0ull); + // Using algorithms for calculating variance over a population: + // http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance + Value sum_squared = sum_ * sum_; + double sum_squared_by_n_squared = + static_cast(sum_squared) / + static_cast(sample_size_ * sample_size_); + double sum_of_squares_by_n = + static_cast(sum_of_squares_) / static_cast(sample_size_); + return sum_of_squares_by_n - sum_squared_by_n_squared; +} + +template +inline void Histogram::PrintBins(std::ostream &os) { + DCHECK_GT(sample_size_, 0ull); + DCHECK(!new_values_added_); + size_t bin_idx = 0; + while (bin_idx < cumulative_freq_.size()) { + if (bin_idx > 0 && + cumulative_perc_[bin_idx] == cumulative_perc_[bin_idx - 1]) { + bin_idx++; + continue; + } + os << ranges_[bin_idx] << ": " << cumulative_freq_[bin_idx] << "\t" + << cumulative_perc_[bin_idx] * 100.0 << "%\n"; + bin_idx++; + } +} + +template +inline void Histogram::PrintConfidenceIntervals(std::ostream &os, + double interval) const { + DCHECK_GT(interval, 0); + DCHECK_LT(interval, 1.0); + + double per_0 = (1.0 - interval) / 2.0; + double per_1 = per_0 + interval; + os << Name() << ":\t"; + TimeUnit unit = GetAppropriateTimeUnit(Mean() * kAdjust); + os << (interval * 100) << "% C.I. " + << FormatDuration(Percentile(per_0) * kAdjust, unit); + os << "-" << FormatDuration(Percentile(per_1) * kAdjust, unit) << " "; + os << "Avg: " << FormatDuration(Mean() * kAdjust, unit) << " Max: "; + os << FormatDuration(Max() * kAdjust, unit) << "\n"; +} + +template inline void Histogram::BuildCDF() { + DCHECK_EQ(cumulative_freq_.size(), 0ull); + DCHECK_EQ(cumulative_perc_.size(), 0ull); + uint64_t accumulated = 0; + + cumulative_freq_.push_back(accumulated); + cumulative_perc_.push_back(0.0); + for (size_t idx = 0; idx < frequency_.size(); idx++) { + accumulated += frequency_[idx]; + cumulative_freq_.push_back(accumulated); + cumulative_perc_.push_back(static_cast(accumulated) / + static_cast(sample_size_)); + } + DCHECK_EQ(*(cumulative_freq_.end() - 1), sample_size_); + DCHECK_EQ(*(cumulative_perc_.end() - 1), 1.0); +} + +template inline void Histogram::CreateHistogram() { + DCHECK_GT(sample_size_, 0ull); + + // Create a histogram only if new values are added. + if (!new_values_added_) + return; + + // Reset cumulative values in case this is not the first time creating histogram. + cumulative_freq_.clear(); + cumulative_perc_.clear(); + BuildCDF(); + new_values_added_ = false; +} + +template +inline double Histogram::Percentile(double per) const { + DCHECK_GT(cumulative_perc_.size(), 0ull); + size_t idx, upper_idx = 0, lower_idx = 0; + for (idx = 0; idx < cumulative_perc_.size(); idx++) { + + if (per <= cumulative_perc_[idx]) { + upper_idx = idx; + break; + } + + if (per >= cumulative_perc_[idx] && idx != 0 && + cumulative_perc_[idx] != cumulative_perc_[idx - 1]) { + lower_idx = idx; + } + } + + double upper_value = static_cast(ranges_[upper_idx]); + double lower_value = static_cast(ranges_[lower_idx]); + + double lower_perc = cumulative_perc_[lower_idx]; + double upper_perc = cumulative_perc_[upper_idx]; + + if (per == lower_perc) { + return lower_value; + } + if (per == upper_perc) { + return upper_value; + } + DCHECK_GT(upper_perc, lower_perc); + + double value = lower_value + (upper_value - lower_value) * + (per - lower_perc) / (upper_perc - lower_perc); + + if (value < min_value_added_) { + value = min_value_added_; + } else if (value > max_value_added_) { + value = max_value_added_; + } + + return value; +} + +} // namespace art +#endif // SRC_BASE_HISTOGRAM_INL_H_ + diff --git a/src/base/histogram.h b/src/base/histogram.h new file mode 100644 index 0000000000000000000000000000000000000000..6878e71cccb51edb5464c149fc78b9fca0d37dfd --- /dev/null +++ b/src/base/histogram.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2013 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_SRC_BASE_HISTOGRAM_H_ +#define ART_SRC_BASE_HISTOGRAM_H_ + +#include +#include + +#include "base/logging.h" +#include "utils.h" + +namespace art { + +// Creates a data histogram for a better understanding of statistical data. +// Histogram analysis goes beyond simple mean and standard deviation to provide +// percentiles values, describing where the $% of the input data lies. +// Designed to be simple and used with timing logger in art. + +template class Histogram { + + const double kAdjust; + const Value kBucketWidth; + const size_t kInitialBucketCount; + + public: + Histogram(std::string); + void AddValue(Value); + void CreateHistogram(); + void Reset(); + double Mean() const; + double Variance() const; + double Percentile(double) const; + void PrintConfidenceIntervals(std::ostream &, double) const; + void PrintBins(std::ostream &); + + uint64_t SampleSize() const { + return sample_size_; + } + + Value Sum() const { + return sum_; + } + + Value Min() const { + return min_value_added_; + } + + Value Max() const { + return max_value_added_; + } + + const std::string &Name() const { + return name_; + } + + + private: + void BuildRanges(); + void Initialize(); + size_t FindBucket(Value); + void BucketiseValue(Value); + // Builds the cumulative distribution function from the frequency data. + // Must be called before using the percentile function. + void BuildCDF(); + // Add more buckets to the histogram to fill in a new value that exceeded + // the max_read_value_. + void GrowBuckets(Value); + bool new_values_added_; + std::string name_; + // Number of samples placed in histogram. + uint64_t sample_size_; + // Width of the bucket range. The lower the value is the more accurate + // histogram percentiles are. + Value bucket_width_; + // Number of bucket to have in the histogram. this value will increase + // to accommodate for big values that don't fit in initial bucket ranges. + size_t bucket_count_; + // Represents the ranges of the histograms. Has SampleSize() + 1 elements + // e.g. 0,5,10,15 represents ranges 0-5, 5-10, 10-15 + std::vector ranges_; + // How many occurrences of values fall within a corresponding range that is + // saved in the ranges_ vector. + std::vector frequency_; + // Accumulative summation of frequencies. + // cumulative_freq_[i] = sum(cumulative_freq_[j] : 0 < j < i ) + std::vector cumulative_freq_; + // Accumulative summation of percentiles; which is the frequency / SampleSize + // cumulative_freq_[i] = sum(cumulative_freq_[j] : 0 < j < i ) + std::vector cumulative_perc_; + // Summation of all the elements inputed by the user. + Value sum_; + // Maximum value that can fit in the histogram, grows adaptively. + Value min_; + // Minimum value that can fit in the histogram. Fixed to zero for now. + Value max_; + // Summation of the values entered. Used to calculate variance. + Value sum_of_squares_; + // Maximum value entered in the histogram. + Value min_value_added_; + // Minimum value entered in the histogram. + Value max_value_added_; + + DISALLOW_COPY_AND_ASSIGN(Histogram); +}; +} + +#endif // ART_SRC_BASE_HISTOGRAM_H_ diff --git a/src/base/histogram_test.cc b/src/base/histogram_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..ea3e35ff936f3dfbe0ce722f030e9b2a7b218d69 --- /dev/null +++ b/src/base/histogram_test.cc @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gtest/gtest.h" +#include "histogram-inl.h" +#include "UniquePtr.h" + +#include + +using namespace art; + +//Simple usage: +// Histogram *hist = new Histogram("SimplePercentiles"); +// Percentile PerValue +// hist->AddValue(121); +// hist->AddValue(132); +// hist->AddValue(140); +// hist->AddValue(145); +// hist->AddValue(155); +// hist->CreateHistogram(); +// PerValue = hist->PercentileVal(0.50); finds the 50th percentile(median). + +TEST(Histtest, MeanTest) { + UniquePtr > hist(new Histogram("MeanTest")); + + double mean; + for (size_t Idx = 0; Idx < 90; Idx++) { + hist->AddValue(static_cast(50)); + } + mean = hist->Mean(); + EXPECT_EQ(mean, 50); + hist->Reset(); + hist->AddValue(9); + hist->AddValue(17); + hist->AddValue(28); + hist->AddValue(28); + mean = hist->Mean(); + EXPECT_EQ(20.5, mean); +} + +TEST(Histtest, VarianceTest) { + UniquePtr > hist(new Histogram("VarianceTest")); + + double variance; + hist->AddValue(9); + hist->AddValue(17); + hist->AddValue(28); + hist->AddValue(28); + hist->CreateHistogram(); + variance = hist->Variance(); + EXPECT_EQ(64.25, variance); +} + +TEST(Histtest, Percentile) { + UniquePtr > hist(new Histogram("Percentile")); + + double PerValue; + + hist->AddValue(20); + hist->AddValue(31); + hist->AddValue(42); + hist->AddValue(50); + hist->AddValue(60); + hist->AddValue(70); + + hist->AddValue(98); + + hist->AddValue(110); + hist->AddValue(121); + hist->AddValue(132); + hist->AddValue(140); + hist->AddValue(145); + hist->AddValue(155); + + hist->CreateHistogram(); + PerValue = hist->Percentile(0.50); + EXPECT_EQ(875, static_cast(PerValue * 10)); +} + +TEST(Histtest, UpdateRange) { + UniquePtr > hist(new Histogram("UpdateRange")); + + double PerValue; + + hist->AddValue(15); + hist->AddValue(17); + hist->AddValue(35); + hist->AddValue(50); + hist->AddValue(68); + hist->AddValue(75); + hist->AddValue(93); + hist->AddValue(110); + hist->AddValue(121); + hist->AddValue(132); + hist->AddValue(140); //Median value + hist->AddValue(145); + hist->AddValue(155); + hist->AddValue(163); + hist->AddValue(168); + hist->AddValue(175); + hist->AddValue(182); + hist->AddValue(193); + hist->AddValue(200); + hist->AddValue(205); + hist->AddValue(212); + hist->CreateHistogram(); + PerValue = hist->Percentile(0.50); + + std::string text; + std::stringstream stream; + std::string expected = + "UpdateRange:\t99% C.I. 15us-212us Avg: 126.380us Max: 212us\n"; + hist->PrintConfidenceIntervals(stream, 0.99); + + EXPECT_EQ(expected, stream.str()); + EXPECT_GE(PerValue, 132); + EXPECT_LE(PerValue, 145); +} +; + +TEST(Histtest, Reset) { + UniquePtr > hist(new Histogram("Reset")); + + double PerValue; + hist->AddValue(0); + hist->AddValue(189); + hist->AddValue(389); + hist->Reset(); + hist->AddValue(15); + hist->AddValue(17); + hist->AddValue(35); + hist->AddValue(50); + hist->AddValue(68); + hist->AddValue(75); + hist->AddValue(93); + hist->AddValue(110); + hist->AddValue(121); + hist->AddValue(132); + hist->AddValue(140); //Median value + hist->AddValue(145); + hist->AddValue(155); + hist->AddValue(163); + hist->AddValue(168); + hist->AddValue(175); + hist->AddValue(182); + hist->AddValue(193); + hist->AddValue(200); + hist->AddValue(205); + hist->AddValue(212); + hist->CreateHistogram(); + PerValue = hist->Percentile(0.50); + + std::string text; + std::stringstream stream; + std::string expected = + "Reset:\t99% C.I. 15us-212us Avg: 126.380us Max: 212us\n"; + hist->PrintConfidenceIntervals(stream, 0.99); + + EXPECT_EQ(expected, stream.str()); + EXPECT_GE(PerValue, 132); + EXPECT_LE(PerValue, 145); +} +; + +TEST(Histtest, MultipleCreateHist) { + UniquePtr > hist(new Histogram("MultipleCreateHist")); + + double PerValue; + hist->AddValue(15); + hist->AddValue(17); + hist->AddValue(35); + hist->AddValue(50); + hist->AddValue(68); + hist->AddValue(75); + hist->AddValue(93); + hist->CreateHistogram(); + hist->AddValue(110); + hist->AddValue(121); + hist->AddValue(132); + hist->AddValue(140); //Median value + hist->AddValue(145); + hist->AddValue(155); + hist->AddValue(163); + hist->AddValue(168); + hist->CreateHistogram(); + hist->AddValue(175); + hist->AddValue(182); + hist->AddValue(193); + hist->AddValue(200); + hist->AddValue(205); + hist->AddValue(212); + hist->CreateHistogram(); + PerValue = hist->Percentile(0.50); + std::stringstream stream; + std::string expected = + "MultipleCreateHist:\t99% C.I. 15us-212us Avg: 126.380us Max: 212us\n"; + hist->PrintConfidenceIntervals(stream, 0.99); + + EXPECT_EQ(expected, stream.str()); + EXPECT_GE(PerValue, 132); + EXPECT_LE(PerValue, 145); +} + +TEST(Histtest, SingleValue) { + UniquePtr > hist(new Histogram("SingleValue")); + + hist->AddValue(1); + hist->CreateHistogram(); + std::stringstream stream; + std::string expected = "SingleValue:\t99% C.I. 1us-1us Avg: 1us Max: 1us\n"; + hist->PrintConfidenceIntervals(stream, 0.99); + EXPECT_EQ(expected, stream.str()); +} + +TEST(Histtest, CappingPercentiles) { + UniquePtr > hist(new Histogram("CappingPercentiles")); + + double per_995; + double per_005; + // All values are similar. + for (uint64_t idx = 0ull; idx < 150ull; idx++) { + hist->AddValue(0); + } + hist->CreateHistogram(); + per_995 = hist->Percentile(0.995); + EXPECT_EQ(per_995, 0); + hist->Reset(); + for (size_t idx = 0; idx < 200; idx++) { + for (uint64_t val = 1ull; val <= 4ull; val++) { + hist->AddValue(val); + } + } + hist->CreateHistogram(); + per_005 = hist->Percentile(0.005); + per_995 = hist->Percentile(0.995); + EXPECT_EQ(1, per_005); + EXPECT_EQ(4, per_995); +} + +TEST(Histtest, SpikyValues) { + UniquePtr > hist(new Histogram("SpikyValues")); + + for (uint64_t idx = 0ull; idx < 30ull; idx++) { + for (uint64_t idx_inner = 0ull; idx_inner < 5ull; idx_inner++) { + hist->AddValue(idx * idx_inner); + } + } + hist->AddValue(10000); + hist->CreateHistogram(); + std::stringstream stream; + std::string expected = + "SpikyValues:\t99% C.I. 0.089us-2541.825us Avg: 95.033us Max: 10000us\n"; + hist->PrintConfidenceIntervals(stream, 0.99); + EXPECT_EQ(expected, stream.str()); +} diff --git a/src/base/logging.cc b/src/base/logging.cc new file mode 100644 index 0000000000000000000000000000000000000000..a19580cbdec80f6e6ea0219a779621b6cd90f5e8 --- /dev/null +++ b/src/base/logging.cc @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "logging.h" + +#include "base/mutex.h" +#include "runtime.h" +#include "thread.h" +#include "utils.h" + +namespace art { + +LogVerbosity gLogVerbosity; + +unsigned int gAborting = 0; + +static LogSeverity gMinimumLogSeverity = INFO; +static std::string* gCmdLine; +static std::string* gProgramInvocationName; +static std::string* gProgramInvocationShortName; + +const char* GetCmdLine() { + return (gCmdLine != NULL) ? gCmdLine->c_str() : NULL; +} + +const char* ProgramInvocationName() { + return (gProgramInvocationName != NULL) ? gProgramInvocationName->c_str() : "art"; +} + +const char* ProgramInvocationShortName() { + return (gProgramInvocationShortName != NULL) ? gProgramInvocationShortName->c_str() : "art"; +} + +// Configure logging based on ANDROID_LOG_TAGS environment variable. +// We need to parse a string that looks like +// +// *:v jdwp:d dalvikvm:d dalvikvm-gc:i dalvikvmi:i +// +// The tag (or '*' for the global level) comes first, followed by a colon +// and a letter indicating the minimum priority level we're expected to log. +// This can be used to reveal or conceal logs with specific tags. +void InitLogging(char* argv[]) { + // TODO: Move this to a more obvious InitART... + Locks::Init(); + + // Stash the command line for later use. We can use /proc/self/cmdline on Linux to recover this, + // but we don't have that luxury on the Mac, and there are a couple of argv[0] variants that are + // commonly used. + gCmdLine = new std::string(argv[0]); + for (size_t i = 1; argv[i] != NULL; ++i) { + gCmdLine->append(" "); + gCmdLine->append(argv[i]); + } + gProgramInvocationName = new std::string(argv[0]); + const char* last_slash = strrchr(argv[0], '/'); + gProgramInvocationShortName = new std::string((last_slash != NULL) ? last_slash + 1 : argv[0]); + + const char* tags = getenv("ANDROID_LOG_TAGS"); + if (tags == NULL) { + return; + } + + std::vector specs; + Split(tags, ' ', specs); + for (size_t i = 0; i < specs.size(); ++i) { + // "tag-pattern:[vdiwefs]" + std::string spec(specs[i]); + if (spec.size() == 3 && StartsWith(spec, "*:")) { + switch (spec[2]) { + case 'v': gMinimumLogSeverity = VERBOSE; continue; + case 'd': gMinimumLogSeverity = DEBUG; continue; + case 'i': gMinimumLogSeverity = INFO; continue; + case 'w': gMinimumLogSeverity = WARNING; continue; + case 'e': gMinimumLogSeverity = ERROR; continue; + case 'f': gMinimumLogSeverity = FATAL; continue; + // liblog will even suppress FATAL if you say 's' for silent, but that's crazy! + case 's': gMinimumLogSeverity = FATAL; continue; + } + } + LOG(FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags << ")"; + } +} + +LogMessageData::LogMessageData(const char* file, int line, LogSeverity severity, int error) + : file(file), + line_number(line), + severity(severity), + error(error) { + const char* last_slash = strrchr(file, '/'); + file = (last_slash == NULL) ? file : last_slash + 1; +} + +LogMessage::~LogMessage() { + if (data_->severity < gMinimumLogSeverity) { + return; // No need to format something we're not going to output. + } + + // Finish constructing the message. + if (data_->error != -1) { + data_->buffer << ": " << strerror(data_->error); + } + std::string msg(data_->buffer.str()); + + // Do the actual logging with the lock held. + { + MutexLock mu(Thread::Current(), *Locks::logging_lock_); + if (msg.find('\n') == std::string::npos) { + LogLine(*data_, msg.c_str()); + } else { + msg += '\n'; + size_t i = 0; + while (i < msg.size()) { + size_t nl = msg.find('\n', i); + msg[nl] = '\0'; + LogLine(*data_, &msg[i]); + i = nl + 1; + } + } + } + + // Abort if necessary. + if (data_->severity == FATAL) { + Runtime::Abort(); + } + + delete data_; +} + +HexDump::HexDump(const void* address, size_t byte_count, bool show_actual_addresses) + : address_(address), byte_count_(byte_count), show_actual_addresses_(show_actual_addresses) { +} + +void HexDump::Dump(std::ostream& os) const { + if (byte_count_ == 0) { + return; + } + + if (address_ == NULL) { + os << "00000000:"; + return; + } + + static const char gHexDigit[] = "0123456789abcdef"; + const unsigned char* addr = reinterpret_cast(address_); + char out[76]; /* exact fit */ + unsigned int offset; /* offset to show while printing */ + + if (show_actual_addresses_) { + offset = reinterpret_cast(addr); + } else { + offset = 0; + } + memset(out, ' ', sizeof(out)-1); + out[8] = ':'; + out[sizeof(out)-1] = '\0'; + + size_t byte_count = byte_count_; + int gap = static_cast(offset & 0x0f); + while (byte_count) { + unsigned int line_offset = offset & ~0x0f; + + char* hex = out; + char* asc = out + 59; + + for (int i = 0; i < 8; i++) { + *hex++ = gHexDigit[line_offset >> 28]; + line_offset <<= 4; + } + hex++; + hex++; + + int count = std::min(static_cast(byte_count), 16 - gap); + CHECK_NE(count, 0); + CHECK_LE(count + gap, 16); + + if (gap) { + /* only on first line */ + hex += gap * 3; + asc += gap; + } + + int i; + for (i = gap ; i < count+gap; i++) { + *hex++ = gHexDigit[*addr >> 4]; + *hex++ = gHexDigit[*addr & 0x0f]; + hex++; + if (*addr >= 0x20 && *addr < 0x7f /*isprint(*addr)*/) { + *asc++ = *addr; + } else { + *asc++ = '.'; + } + addr++; + } + for (; i < 16; i++) { + /* erase extra stuff; only happens on last line */ + *hex++ = ' '; + *hex++ = ' '; + hex++; + *asc++ = ' '; + } + + os << out; + + gap = 0; + byte_count -= count; + offset += count; + } +} + +std::ostream& operator<<(std::ostream& os, const HexDump& rhs) { + rhs.Dump(os); + return os; +} + +} // namespace art diff --git a/src/base/logging.h b/src/base/logging.h new file mode 100644 index 0000000000000000000000000000000000000000..8d89e4d0cbdb7cef21f4061ec0a8013db09de3e8 --- /dev/null +++ b/src/base/logging.h @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2011 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_SRC_BASE_LOGGING_H_ +#define ART_SRC_BASE_LOGGING_H_ + +#include +#include +#include // NOLINT +#include +#include +#include "base/macros.h" +#include "log_severity.h" + +#define CHECK(x) \ + if (UNLIKELY(!(x))) \ + ::art::LogMessage(__FILE__, __LINE__, FATAL, -1).stream() \ + << "Check failed: " #x << " " + +#define CHECK_OP(LHS, RHS, OP) \ + for (::art::EagerEvaluator _values(LHS, RHS); \ + UNLIKELY(!(_values.lhs OP _values.rhs)); /* empty */) \ + ::art::LogMessage(__FILE__, __LINE__, FATAL, -1).stream() \ + << "Check failed: " << #LHS << " " << #OP << " " << #RHS \ + << " (" #LHS "=" << _values.lhs << ", " #RHS "=" << _values.rhs << ") " + +#define CHECK_EQ(x, y) CHECK_OP(x, y, ==) +#define CHECK_NE(x, y) CHECK_OP(x, y, !=) +#define CHECK_LE(x, y) CHECK_OP(x, y, <=) +#define CHECK_LT(x, y) CHECK_OP(x, y, <) +#define CHECK_GE(x, y) CHECK_OP(x, y, >=) +#define CHECK_GT(x, y) CHECK_OP(x, y, >) + +#define CHECK_STROP(s1, s2, sense) \ + if (UNLIKELY((strcmp(s1, s2) == 0) != sense)) \ + LOG(FATAL) << "Check failed: " \ + << "\"" << s1 << "\"" \ + << (sense ? " == " : " != ") \ + << "\"" << s2 << "\"" + +#define CHECK_STREQ(s1, s2) CHECK_STROP(s1, s2, true) +#define CHECK_STRNE(s1, s2) CHECK_STROP(s1, s2, false) + +#define CHECK_PTHREAD_CALL(call, args, what) \ + do { \ + int rc = call args; \ + if (rc != 0) { \ + errno = rc; \ + PLOG(FATAL) << # call << " failed for " << what; \ + } \ + } while (false) + +#ifndef NDEBUG + +#define DCHECK(x) CHECK(x) +#define DCHECK_EQ(x, y) CHECK_EQ(x, y) +#define DCHECK_NE(x, y) CHECK_NE(x, y) +#define DCHECK_LE(x, y) CHECK_LE(x, y) +#define DCHECK_LT(x, y) CHECK_LT(x, y) +#define DCHECK_GE(x, y) CHECK_GE(x, y) +#define DCHECK_GT(x, y) CHECK_GT(x, y) +#define DCHECK_STREQ(s1, s2) CHECK_STREQ(s1, s2) +#define DCHECK_STRNE(s1, s2) CHECK_STRNE(s1, s2) + +#else // NDEBUG + +#define DCHECK(condition) \ + while (false) \ + CHECK(condition) + +#define DCHECK_EQ(val1, val2) \ + while (false) \ + CHECK_EQ(val1, val2) + +#define DCHECK_NE(val1, val2) \ + while (false) \ + CHECK_NE(val1, val2) + +#define DCHECK_LE(val1, val2) \ + while (false) \ + CHECK_LE(val1, val2) + +#define DCHECK_LT(val1, val2) \ + while (false) \ + CHECK_LT(val1, val2) + +#define DCHECK_GE(val1, val2) \ + while (false) \ + CHECK_GE(val1, val2) + +#define DCHECK_GT(val1, val2) \ + while (false) \ + CHECK_GT(val1, val2) + +#define DCHECK_STREQ(str1, str2) \ + while (false) \ + CHECK_STREQ(str1, str2) + +#define DCHECK_STRNE(str1, str2) \ + while (false) \ + CHECK_STRNE(str1, str2) + +#endif + +#define LOG(severity) ::art::LogMessage(__FILE__, __LINE__, severity, -1).stream() +#define PLOG(severity) ::art::LogMessage(__FILE__, __LINE__, severity, errno).stream() + +#define LG LOG(INFO) + +#define UNIMPLEMENTED(level) LOG(level) << __PRETTY_FUNCTION__ << " unimplemented " + +#define VLOG_IS_ON(module) UNLIKELY(::art::gLogVerbosity.module) +#define VLOG(module) if (VLOG_IS_ON(module)) ::art::LogMessage(__FILE__, __LINE__, INFO, -1).stream() + +// +// Implementation details beyond this point. +// + +namespace art { + +template +struct EagerEvaluator { + EagerEvaluator(LHS lhs, RHS rhs) : lhs(lhs), rhs(rhs) { } + LHS lhs; + RHS rhs; +}; + +// We want char*s to be treated as pointers, not strings. If you want them treated like strings, +// you'd need to use CHECK_STREQ and CHECK_STRNE anyway to compare the characters rather than their +// addresses. We could express this more succinctly with std::remove_const, but this is quick and +// easy to understand, and works before we have C++0x. We rely on signed/unsigned warnings to +// protect you against combinations not explicitly listed below. +#define EAGER_PTR_EVALUATOR(T1, T2) \ + template <> struct EagerEvaluator { \ + EagerEvaluator(T1 lhs, T2 rhs) \ + : lhs(reinterpret_cast(lhs)), \ + rhs(reinterpret_cast(rhs)) { } \ + const void* lhs; \ + const void* rhs; \ + } +EAGER_PTR_EVALUATOR(const char*, const char*); +EAGER_PTR_EVALUATOR(const char*, char*); +EAGER_PTR_EVALUATOR(char*, const char*); +EAGER_PTR_EVALUATOR(char*, char*); +EAGER_PTR_EVALUATOR(const unsigned char*, const unsigned char*); +EAGER_PTR_EVALUATOR(const unsigned char*, unsigned char*); +EAGER_PTR_EVALUATOR(unsigned char*, const unsigned char*); +EAGER_PTR_EVALUATOR(unsigned char*, unsigned char*); +EAGER_PTR_EVALUATOR(const signed char*, const signed char*); +EAGER_PTR_EVALUATOR(const signed char*, signed char*); +EAGER_PTR_EVALUATOR(signed char*, const signed char*); +EAGER_PTR_EVALUATOR(signed char*, signed char*); + +// This indirection greatly reduces the stack impact of having +// lots of checks/logging in a function. +struct LogMessageData { + public: + LogMessageData(const char* file, int line, LogSeverity severity, int error); + std::ostringstream buffer; + const char* const file; + const int line_number; + const LogSeverity severity; + const int error; + + private: + DISALLOW_COPY_AND_ASSIGN(LogMessageData); +}; + +class LogMessage { + public: + LogMessage(const char* file, int line, LogSeverity severity, int error) + : data_(new LogMessageData(file, line, severity, error)) { + } + + ~LogMessage() LOCKS_EXCLUDED(Locks::logging_lock_); + + std::ostream& stream() { + return data_->buffer; + } + + private: + static void LogLine(const LogMessageData& data, const char*); + + LogMessageData* const data_; + + friend void HandleUnexpectedSignal(int signal_number, siginfo_t* info, void* raw_context); + friend class Mutex; + DISALLOW_COPY_AND_ASSIGN(LogMessage); +}; + +// Prints a hex dump in this format: +// +// 01234560: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef +// 01234568: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef +class HexDump { + public: + HexDump(const void* address, size_t byte_count, bool show_actual_addresses = false); + void Dump(std::ostream& os) const; + + private: + const void* address_; + size_t byte_count_; + bool show_actual_addresses_; + + // TODO: Remove the #if when Mac OS build server no longer uses GCC 4.2.*. +#if GCC_VERSION >= 40300 + DISALLOW_COPY_AND_ASSIGN(HexDump); +#endif +}; +std::ostream& operator<<(std::ostream& os, const HexDump& rhs); + +// A convenience to allow any class with a "Dump(std::ostream& os)" member function +// but without an operator<< to be used as if it had an operator<<. Use like this: +// +// os << Dumpable(my_type_instance); +// +template +class Dumpable { + public: + explicit Dumpable(T& value) : value_(value) { + } + + void Dump(std::ostream& os) const { + value_.Dump(os); + } + + private: + T& value_; + +// TODO: Remove the #if when Mac OS build server no longer uses GCC 4.2.*. +#if GCC_VERSION >= 40300 + DISALLOW_COPY_AND_ASSIGN(Dumpable); +#endif +}; + +template +std::ostream& operator<<(std::ostream& os, const Dumpable& rhs) { + rhs.Dump(os); + return os; +} + +template +class MutatorLockedDumpable { + public: + explicit MutatorLockedDumpable(T& value) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) : value_(value) { + } + + void Dump(std::ostream& os) const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + value_.Dump(os); + } + + private: + T& value_; + +// TODO: Remove the #if when Mac OS build server no longer uses GCC 4.2.*. +#if GCC_VERSION >= 40300 + DISALLOW_COPY_AND_ASSIGN(MutatorLockedDumpable); +#endif +}; + +template +std::ostream& operator<<(std::ostream& os, const MutatorLockedDumpable& rhs) +// TODO: should be SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) however annotalysis +// currently fails for this. + NO_THREAD_SAFETY_ANALYSIS { + rhs.Dump(os); + return os; +} + +// Helps you use operator<< in a const char*-like context such as our various 'F' methods with +// format strings. +template +class ToStr { + public: + explicit ToStr(const T& value) { + std::ostringstream os; + os << value; + s_ = os.str(); + } + + const char* c_str() const { + return s_.c_str(); + } + + const std::string& str() const { + return s_; + } + + private: + std::string s_; + DISALLOW_COPY_AND_ASSIGN(ToStr); +}; + +// The members of this struct are the valid arguments to VLOG and VLOG_IS_ON in code, +// and the "-verbose:" command line argument. +struct LogVerbosity { + bool class_linker; // Enabled with "-verbose:class". + bool compiler; + bool heap; + bool gc; + bool jdwp; + bool jni; + bool monitor; + bool startup; + bool third_party_jni; // Enabled with "-verbose:third-party-jni". + bool threads; +}; + +extern LogVerbosity gLogVerbosity; + +// Used on fatal exit. Prevents recursive aborts. Allows us to disable +// some error checking to ensure fatal shutdown makes forward progress. +extern unsigned int gAborting; + +extern void InitLogging(char* argv[]); + +extern const char* GetCmdLine(); +extern const char* ProgramInvocationName(); +extern const char* ProgramInvocationShortName(); + +} // namespace art + +#endif // ART_SRC_BASE_LOGGING_H_ diff --git a/src/base/logging_android.cc b/src/base/logging_android.cc new file mode 100644 index 0000000000000000000000000000000000000000..9b1ac58461facd807897e49cabb28e548274d43d --- /dev/null +++ b/src/base/logging_android.cc @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "logging.h" + +#include + +#include + +#include "base/stringprintf.h" +#include "cutils/log.h" + +namespace art { + +static const int kLogSeverityToAndroidLogPriority[] = { + ANDROID_LOG_VERBOSE, ANDROID_LOG_DEBUG, ANDROID_LOG_INFO, ANDROID_LOG_WARN, + ANDROID_LOG_ERROR, ANDROID_LOG_FATAL, ANDROID_LOG_FATAL +}; + +void LogMessage::LogLine(const LogMessageData& data, const char* message) { + const char* tag = ProgramInvocationShortName(); + int priority = kLogSeverityToAndroidLogPriority[data.severity]; + if (priority == ANDROID_LOG_FATAL) { + LOG_PRI(priority, tag, "%s:%d] %s", data.file, data.line_number, message); + } else { + LOG_PRI(priority, tag, "%s", message); + } +} + +} // namespace art diff --git a/src/base/logging_linux.cc b/src/base/logging_linux.cc new file mode 100644 index 0000000000000000000000000000000000000000..0399128d65cffbc9b50f025edd08da3fa5adc6f9 --- /dev/null +++ b/src/base/logging_linux.cc @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "logging.h" + +#include +#include + +#include +#include +#include + +#include "base/stringprintf.h" +#include "utils.h" + +namespace art { + +void LogMessage::LogLine(const LogMessageData& data, const char* message) { + char severity = "VDIWEFF"[data.severity]; + fprintf(stderr, "%s %c %5d %5d %s:%d] %s\n", + ProgramInvocationShortName(), severity, getpid(), ::art::GetTid(), + data.file, data.line_number, message); +} + +} // namespace art diff --git a/src/base/macros.h b/src/base/macros.h new file mode 100644 index 0000000000000000000000000000000000000000..8579872d58767d7bb9da64291446c3f3e7d4dd98 --- /dev/null +++ b/src/base/macros.h @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2010 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_SRC_BASE_MACROS_H_ +#define ART_SRC_BASE_MACROS_H_ + +#include // for size_t + +#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) + +// The COMPILE_ASSERT macro can be used to verify that a compile time +// expression is true. For example, you could use it to verify the +// size of a static array: +// +// COMPILE_ASSERT(ARRAYSIZE(content_type_names) == CONTENT_NUM_TYPES, +// content_type_names_incorrect_size); +// +// or to make sure a struct is smaller than a certain size: +// +// COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large); +// +// The second argument to the macro is the name of the variable. If +// the expression is false, most compilers will issue a warning/error +// containing the name of the variable. + +template +struct CompileAssert { +}; + +#define COMPILE_ASSERT(expr, msg) \ + typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] // NOLINT + +// DISALLOW_COPY_AND_ASSIGN disallows the copy and operator= functions. +// It goes in the private: declarations in a class. +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +// +// This should be used in the private: declarations for a class +// that wants to prevent anyone from instantiating it. This is +// especially useful for classes containing only static methods. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName(); \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// The arraysize(arr) macro returns the # of elements in an array arr. +// The expression is a compile-time constant, and therefore can be +// used in defining new arrays, for example. If you use arraysize on +// a pointer by mistake, you will get a compile-time error. +// +// One caveat is that arraysize() doesn't accept any array of an +// anonymous type or a type defined inside a function. In these rare +// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is +// due to a limitation in C++'s template system. The limitation might +// eventually be removed, but it hasn't happened yet. + +// This template function declaration is used in defining arraysize. +// Note that the function doesn't need an implementation, as we only +// use its type. +template +char (&ArraySizeHelper(T (&array)[N]))[N]; + +#define arraysize(array) (sizeof(ArraySizeHelper(array))) + +// ARRAYSIZE_UNSAFE performs essentially the same calculation as arraysize, +// but can be used on anonymous types or types defined inside +// functions. It's less safe than arraysize as it accepts some +// (although not all) pointers. Therefore, you should use arraysize +// whenever possible. +// +// The expression ARRAYSIZE_UNSAFE(a) is a compile-time constant of type +// size_t. +// +// ARRAYSIZE_UNSAFE catches a few type errors. If you see a compiler error +// +// "warning: division by zero in ..." +// +// when using ARRAYSIZE_UNSAFE, you are (wrongfully) giving it a pointer. +// You should only use ARRAYSIZE_UNSAFE on statically allocated arrays. +// +// The following comments are on the implementation details, and can +// be ignored by the users. +// +// ARRAYSIZE_UNSAFE(arr) works by inspecting sizeof(arr) (the # of bytes in +// the array) and sizeof(*(arr)) (the # of bytes in one array +// element). If the former is divisible by the latter, perhaps arr is +// indeed an array, in which case the division result is the # of +// elements in the array. Otherwise, arr cannot possibly be an array, +// and we generate a compiler error to prevent the code from +// compiling. +// +// Since the size of bool is implementation-defined, we need to cast +// !(sizeof(a) & sizeof(*(a))) to size_t in order to ensure the final +// result has type size_t. +// +// This macro is not perfect as it wrongfully accepts certain +// pointers, namely where the pointer size is divisible by the pointee +// size. Since all our code has to go through a 32-bit compiler, +// where a pointer is 4 bytes, this means all pointers to a type whose +// size is 3 or greater than 4 will be (righteously) rejected. +#define ARRAYSIZE_UNSAFE(a) \ + ((sizeof(a) / sizeof(*(a))) / static_cast(!(sizeof(a) % sizeof(*(a))))) + +#define SIZEOF_MEMBER(t, f) sizeof((reinterpret_cast(4096))->f) + +#define OFFSETOF_MEMBER(t, f) \ + (reinterpret_cast(&reinterpret_cast(16)->f) - reinterpret_cast(16)) // NOLINT + +#define OFFSETOF_VOLATILE_MEMBER(t, f) \ + (reinterpret_cast(&reinterpret_cast(16)->f) - reinterpret_cast(16)) // NOLINT + +#define PACKED(x) __attribute__ ((__aligned__(x),__packed__)) + +#define LIKELY(x) __builtin_expect((x), true) +#define UNLIKELY(x) __builtin_expect((x), false) + +#ifndef NDEBUG +#define ALWAYS_INLINE +#else +#define ALWAYS_INLINE __attribute__ ((always_inline)) +#endif + +// bionic and glibc both have TEMP_FAILURE_RETRY, but Mac OS' libc doesn't. +#ifndef TEMP_FAILURE_RETRY +#define TEMP_FAILURE_RETRY(exp) ({ \ + typeof(exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; }) +#endif + +template void UNUSED(const T&) {} + +#if defined(__SUPPORT_TS_ANNOTATION__) + +#define ACQUIRED_AFTER(...) __attribute__ ((acquired_after(__VA_ARGS__))) +#define ACQUIRED_BEFORE(...) __attribute__ ((acquired_before(__VA_ARGS__))) +#define EXCLUSIVE_LOCK_FUNCTION(...) __attribute__ ((exclusive_lock(__VA_ARGS__))) +#define EXCLUSIVE_LOCKS_REQUIRED(...) __attribute__ ((exclusive_locks_required(__VA_ARGS__))) +#define EXCLUSIVE_TRYLOCK_FUNCTION(...) __attribute__ ((exclusive_trylock(__VA_ARGS__))) +#define GUARDED_BY(x) __attribute__ ((guarded_by(x))) +#define GUARDED_VAR __attribute__ ((guarded)) +#define LOCKABLE __attribute__ ((lockable)) +#define LOCK_RETURNED(x) __attribute__ ((lock_returned(x))) +#define LOCKS_EXCLUDED(...) __attribute__ ((locks_excluded(__VA_ARGS__))) +#define NO_THREAD_SAFETY_ANALYSIS __attribute__ ((no_thread_safety_analysis)) +#define PT_GUARDED_BY(x) __attribute__ ((point_to_guarded_by(x))) +#define PT_GUARDED_VAR __attribute__ ((point_to_guarded)) +#define SCOPED_LOCKABLE __attribute__ ((scoped_lockable)) +#define SHARED_LOCK_FUNCTION(...) __attribute__ ((shared_lock(__VA_ARGS__))) +#define SHARED_LOCKS_REQUIRED(...) __attribute__ ((shared_locks_required(__VA_ARGS__))) +#define SHARED_TRYLOCK_FUNCTION(...) __attribute__ ((shared_trylock(__VA_ARGS__))) +#define UNLOCK_FUNCTION(...) __attribute__ ((unlock(__VA_ARGS__))) + +#else + +#define ACQUIRED_AFTER(...) +#define ACQUIRED_BEFORE(...) +#define EXCLUSIVE_LOCK_FUNCTION(...) +#define EXCLUSIVE_LOCKS_REQUIRED(...) +#define EXCLUSIVE_TRYLOCK_FUNCTION(...) +#define GUARDED_BY(x) +#define GUARDED_VAR +#define LOCKABLE +#define LOCK_RETURNED(x) +#define LOCKS_EXCLUDED(...) +#define NO_THREAD_SAFETY_ANALYSIS +#define PT_GUARDED_BY(x) +#define PT_GUARDED_VAR +#define SCOPED_LOCKABLE +#define SHARED_LOCK_FUNCTION(...) +#define SHARED_LOCKS_REQUIRED(...) +#define SHARED_TRYLOCK_FUNCTION(...) +#define UNLOCK_FUNCTION(...) + +#endif // defined(__SUPPORT_TS_ANNOTATION__) + +#endif // ART_SRC_BASE_MACROS_H_ diff --git a/src/base/mutex-inl.h b/src/base/mutex-inl.h new file mode 100644 index 0000000000000000000000000000000000000000..f911054b86daf94a4d105427a7da8b53fb6b66c5 --- /dev/null +++ b/src/base/mutex-inl.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2011 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_SRC_BASE_MUTEX_INL_H_ +#define ART_SRC_BASE_MUTEX_INL_H_ + +#include "mutex.h" + +#include "cutils/atomic-inline.h" +#include "runtime.h" +#include "thread.h" + +namespace art { + +#define CHECK_MUTEX_CALL(call, args) CHECK_PTHREAD_CALL(call, args, name_) + +#if ART_USE_FUTEXES +#include "linux/futex.h" +#include "sys/syscall.h" +#ifndef SYS_futex +#define SYS_futex __NR_futex +#endif +static inline int futex(volatile int *uaddr, int op, int val, const struct timespec *timeout, volatile int *uaddr2, int val3) { + return syscall(SYS_futex, uaddr, op, val, timeout, uaddr2, val3); +} +#endif // ART_USE_FUTEXES + +class ScopedContentionRecorder { + public: +#if CONTENTION_LOGGING + ScopedContentionRecorder(BaseMutex* mutex, uint64_t blocked_tid, uint64_t owner_tid) : + mutex_(mutex), blocked_tid_(blocked_tid), owner_tid_(owner_tid), + start_milli_time_(MilliTime()) { + } +#else + ScopedContentionRecorder(BaseMutex*, uint64_t, uint64_t) {} +#endif + + ~ScopedContentionRecorder() { +#if CONTENTION_LOGGING + uint64_t end_milli_time = MilliTime(); + mutex_->RecordContention(blocked_tid_, owner_tid_, end_milli_time - start_milli_time_); +#endif + } + + private: +#if CONTENTION_LOGGING + BaseMutex* const mutex_; + const uint64_t blocked_tid_; + const uint64_t owner_tid_; + const uint64_t start_milli_time_; +#endif +}; + +static inline uint64_t SafeGetTid(const Thread* self) { + if (self != NULL) { + return static_cast(self->GetTid()); + } else { + return static_cast(GetTid()); + } +} + +static inline void CheckUnattachedThread(LockLevel level) NO_THREAD_SAFETY_ANALYSIS { + // The check below enumerates the cases where we expect not to be able to sanity check locks + // on a thread. Lock checking is disabled to avoid deadlock when checking shutdown lock. + // TODO: tighten this check. + if (kDebugLocking) { + Runtime* runtime = Runtime::Current(); + CHECK(runtime == NULL || !runtime->IsStarted() || runtime->IsShuttingDown() || + level == kDefaultMutexLevel || level == kRuntimeShutdownLock || + level == kThreadListLock || level == kLoggingLock || level == kAbortLock); + } +} + +inline void BaseMutex::RegisterAsLocked(Thread* self) { + if (UNLIKELY(self == NULL)) { + CheckUnattachedThread(level_); + return; + } + if (kDebugLocking) { + // Check if a bad Mutex of this level or lower is held. + bool bad_mutexes_held = false; + for (int i = level_; i >= 0; --i) { + BaseMutex* held_mutex = self->GetHeldMutex(static_cast(i)); + if (UNLIKELY(held_mutex != NULL)) { + LOG(ERROR) << "Lock level violation: holding \"" << held_mutex->name_ << "\" " + << "(level " << LockLevel(i) << " - " << i + << ") while locking \"" << name_ << "\" " + << "(level " << level_ << " - " << static_cast(level_) << ")"; + if (i > kAbortLock) { + // Only abort in the check below if this is more than abort level lock. + bad_mutexes_held = true; + } + } + } + CHECK(!bad_mutexes_held); + } + // Don't record monitors as they are outside the scope of analysis. They may be inspected off of + // the monitor list. + if (level_ != kMonitorLock) { + self->SetHeldMutex(level_, this); + } +} + +inline void BaseMutex::RegisterAsUnlocked(Thread* self) { + if (UNLIKELY(self == NULL)) { + CheckUnattachedThread(level_); + return; + } + if (level_ != kMonitorLock) { + if (kDebugLocking && !gAborting) { + CHECK(self->GetHeldMutex(level_) == this) << "Unlocking on unacquired mutex: " << name_; + } + self->SetHeldMutex(level_, NULL); + } +} + +inline void ReaderWriterMutex::SharedLock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (LIKELY(cur_state >= 0)) { + // Add as an extra reader. + done = android_atomic_acquire_cas(cur_state, cur_state + 1, &state_) == 0; + } else { + // Owner holds it exclusively, hang up. + ScopedContentionRecorder scr(this, GetExclusiveOwnerTid(), SafeGetTid(self)); + android_atomic_inc(&num_pending_readers_); + if (futex(&state_, FUTEX_WAIT, cur_state, NULL, NULL, 0) != 0) { + if (errno != EAGAIN) { + PLOG(FATAL) << "futex wait failed for " << name_; + } + } + android_atomic_dec(&num_pending_readers_); + } + } while(!done); +#else + CHECK_MUTEX_CALL(pthread_rwlock_rdlock, (&rwlock_)); +#endif + RegisterAsLocked(self); + AssertSharedHeld(self); +} + +inline void ReaderWriterMutex::SharedUnlock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + AssertSharedHeld(self); + RegisterAsUnlocked(self); +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (LIKELY(cur_state > 0)) { + // Reduce state by 1. + done = android_atomic_release_cas(cur_state, cur_state - 1, &state_) == 0; + if (done && (cur_state - 1) == 0) { // cas may fail due to noise? + if (num_pending_writers_ > 0 || num_pending_readers_ > 0) { + // Wake any exclusive waiters as there are now no readers. + futex(&state_, FUTEX_WAKE, -1, NULL, NULL, 0); + } + } + } else { + LOG(FATAL) << "Unexpected state_:" << cur_state << " for " << name_; + } + } while(!done); +#else + CHECK_MUTEX_CALL(pthread_rwlock_unlock, (&rwlock_)); +#endif +} + +} // namespace art + +#endif // ART_SRC_BASE_MUTEX_INL_H_ diff --git a/src/base/mutex.cc b/src/base/mutex.cc new file mode 100644 index 0000000000000000000000000000000000000000..a2851e5b3ccf4b8f756988f9e129d98fa99a4406 --- /dev/null +++ b/src/base/mutex.cc @@ -0,0 +1,859 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mutex.h" + +#include +#include + +#include "base/logging.h" +#include "cutils/atomic.h" +#include "cutils/atomic-inline.h" +#include "mutex-inl.h" +#include "runtime.h" +#include "scoped_thread_state_change.h" +#include "thread-inl.h" +#include "utils.h" + +namespace art { + +// This works on Mac OS 10.6 but hasn't been tested on older releases. +struct __attribute__((__may_alias__)) darwin_pthread_mutex_t { + long padding0; + int padding1; + uint32_t padding2; + int16_t padding3; + int16_t padding4; + uint32_t padding5; + pthread_t darwin_pthread_mutex_owner; + // ...other stuff we don't care about. +}; + +struct __attribute__((__may_alias__)) darwin_pthread_rwlock_t { + long padding0; + pthread_mutex_t padding1; + int padding2; + pthread_cond_t padding3; + pthread_cond_t padding4; + int padding5; + int padding6; + pthread_t darwin_pthread_rwlock_owner; + // ...other stuff we don't care about. +}; + +struct __attribute__((__may_alias__)) glibc_pthread_mutex_t { + int32_t padding0[2]; + int owner; + // ...other stuff we don't care about. +}; + +struct __attribute__((__may_alias__)) glibc_pthread_rwlock_t { +#ifdef __LP64__ + int32_t padding0[6]; +#else + int32_t padding0[7]; +#endif + int writer; + // ...other stuff we don't care about. +}; + +#if ART_USE_FUTEXES +static bool ComputeRelativeTimeSpec(timespec* result_ts, const timespec& lhs, const timespec& rhs) { + const long int one_sec = 1000 * 1000 * 1000; // one second in nanoseconds. + result_ts->tv_sec = lhs.tv_sec - rhs.tv_sec; + result_ts->tv_nsec = lhs.tv_nsec - rhs.tv_nsec; + if (result_ts->tv_nsec < 0) { + result_ts->tv_sec--; + result_ts->tv_nsec += one_sec; + } else if (result_ts->tv_nsec > one_sec) { + result_ts->tv_sec++; + result_ts->tv_nsec -= one_sec; + } + return result_ts->tv_sec < 0; +} +#endif + +#if CONTENTION_LOGGING +// A guard for all_mutexes_ that's not a mutex (Mutexes must CAS to acquire and busy wait). +static AtomicInteger all_mutexes_guard_; +// All created mutexes guarded by all_mutexes_guard_. +std::set* all_mutexes_; + +class ScopedAllMutexesLock { + public: + ScopedAllMutexesLock(const BaseMutex* mutex) : mutex_(mutex) { + while (!all_mutexes_guard_.CompareAndSwap(0, reinterpret_cast(mutex))) { + NanoSleep(100); + } + } + ~ScopedAllMutexesLock() { + while (!all_mutexes_guard_.CompareAndSwap(reinterpret_cast(mutex_), 0)) { + NanoSleep(100); + } + } + private: + const BaseMutex* const mutex_; +}; +#endif + +BaseMutex::BaseMutex(const char* name, LockLevel level) : level_(level), name_(name) { +#if CONTENTION_LOGGING + ScopedAllMutexesLock mu(this); + if (all_mutexes_ == NULL) { + // We leak the global set of all mutexes to avoid ordering issues in global variable + // construction/destruction. + all_mutexes_ = new std::set(); + } + all_mutexes_->insert(this); +#endif +} + +BaseMutex::~BaseMutex() { +#if CONTENTION_LOGGING + ScopedAllMutexesLock mu(this); + all_mutexes_->erase(this); +#endif +} + +void BaseMutex::DumpAll(std::ostream& os) { +#if CONTENTION_LOGGING + os << "Mutex logging:\n"; + ScopedAllMutexesLock mu(reinterpret_cast(-1)); + typedef std::set::const_iterator It; + for (It it = all_mutexes_->begin(); it != all_mutexes_->end(); ++it) { + BaseMutex* mutex = *it; + mutex->Dump(os); + os << "\n"; + } +#endif +} + +void BaseMutex::CheckSafeToWait(Thread* self) { + if (self == NULL) { + CheckUnattachedThread(level_); + return; + } + if (kDebugLocking) { + CHECK(self->GetHeldMutex(level_) == this) << "Waiting on unacquired mutex: " << name_; + bool bad_mutexes_held = false; + for (int i = kLockLevelCount - 1; i >= 0; --i) { + if (i != level_) { + BaseMutex* held_mutex = self->GetHeldMutex(static_cast(i)); + if (held_mutex != NULL) { + LOG(ERROR) << "Holding \"" << held_mutex->name_ << "\" " + << "(level " << LockLevel(i) << ") while performing wait on " + << "\"" << name_ << "\" (level " << level_ << ")"; + bad_mutexes_held = true; + } + } + } + CHECK(!bad_mutexes_held); + } +} + +void BaseMutex::RecordContention(uint64_t blocked_tid, uint64_t owner_tid, uint64_t milli_time_blocked) { +#if CONTENTION_LOGGING + ++contention_count_; + wait_time_ += static_cast(milli_time_blocked); // May overflow. + // This code is intentionally racy as it is only used for diagnostics. + uint32_t slot = cur_content_log_entry_; + if (contention_log_[slot].blocked_tid == blocked_tid && + contention_log_[slot].owner_tid == blocked_tid) { + ++contention_log_[slot].count; + } else { + uint32_t new_slot; + do { + slot = cur_content_log_entry_; + new_slot = (slot + 1) % kContentionLogSize; + } while(!cur_content_log_entry_.CompareAndSwap(slot, new_slot)); + contention_log_[new_slot].blocked_tid = blocked_tid; + contention_log_[new_slot].owner_tid = owner_tid; + contention_log_[new_slot].count = 1; + } +#endif +} + +void BaseMutex::DumpContention(std::ostream& os) const { +#if CONTENTION_LOGGING + uint32_t wait_time = wait_time_; + uint32_t contention_count = contention_count_; + if (contention_count == 0) { + os << "never contended"; + } else { + os << "contended " << contention_count << " times, average wait of contender " << (wait_time / contention_count) << "ms"; + SafeMap most_common_blocker; + SafeMap most_common_blocked; + typedef SafeMap::const_iterator It; + for (size_t i = 0; i < kContentionLogSize; ++i) { + uint64_t blocked_tid = contention_log_[i].blocked_tid; + uint64_t owner_tid = contention_log_[i].owner_tid; + uint32_t count = contention_log_[i].count; + if (count > 0) { + It it = most_common_blocked.find(blocked_tid); + if (it != most_common_blocked.end()) { + most_common_blocked.Overwrite(blocked_tid, it->second + count); + } else { + most_common_blocked.Put(blocked_tid, count); + } + it = most_common_blocker.find(owner_tid); + if (it != most_common_blocker.end()) { + most_common_blocker.Overwrite(owner_tid, it->second + count); + } else { + most_common_blocker.Put(owner_tid, count); + } + } + } + uint64_t max_tid = 0; + size_t max_tid_count = 0; + for (It it = most_common_blocked.begin(); it != most_common_blocked.end(); ++it) { + if (it->second > max_tid_count) { + max_tid = it->first; + max_tid_count = it->second; + } + } + if (max_tid != 0) { + os << " sample shows most blocked tid=" << max_tid; + } + max_tid = 0; + max_tid_count = 0; + for (It it = most_common_blocker.begin(); it != most_common_blocker.end(); ++it) { + if (it->second > max_tid_count) { + max_tid = it->first; + max_tid_count = it->second; + } + } + if (max_tid != 0) { + os << " sample shows tid=" << max_tid << " owning during this time"; + } + } +#endif +} + + +Mutex::Mutex(const char* name, LockLevel level, bool recursive) + : BaseMutex(name, level), recursive_(recursive), recursion_count_(0) { +#if ART_USE_FUTEXES + state_ = 0; + exclusive_owner_ = 0; + num_contenders_ = 0; +#elif defined(__BIONIC__) || defined(__APPLE__) + // Use recursive mutexes for bionic and Apple otherwise the + // non-recursive mutexes don't have TIDs to check lock ownership of. + pthread_mutexattr_t attributes; + CHECK_MUTEX_CALL(pthread_mutexattr_init, (&attributes)); + CHECK_MUTEX_CALL(pthread_mutexattr_settype, (&attributes, PTHREAD_MUTEX_RECURSIVE)); + CHECK_MUTEX_CALL(pthread_mutex_init, (&mutex_, &attributes)); + CHECK_MUTEX_CALL(pthread_mutexattr_destroy, (&attributes)); +#else + CHECK_MUTEX_CALL(pthread_mutex_init, (&mutex_, NULL)); +#endif +} + +Mutex::~Mutex() { +#if ART_USE_FUTEXES + if (state_ != 0) { + MutexLock mu(Thread::Current(), *Locks::runtime_shutdown_lock_); + Runtime* runtime = Runtime::Current(); + bool shutting_down = (runtime == NULL) || runtime->IsShuttingDown(); + LOG(shutting_down ? WARNING : FATAL) << "destroying mutex with owner: " << exclusive_owner_; + } else { + CHECK_EQ(exclusive_owner_, 0U) << "unexpectedly found an owner on unlocked mutex " << name_; + CHECK_EQ(num_contenders_, 0) << "unexpectedly found a contender on mutex " << name_; + } +#else + // We can't use CHECK_MUTEX_CALL here because on shutdown a suspended daemon thread + // may still be using locks. + int rc = pthread_mutex_destroy(&mutex_); + if (rc != 0) { + errno = rc; + // TODO: should we just not log at all if shutting down? this could be the logging mutex! + MutexLock mu(Thread::Current(), *Locks::runtime_shutdown_lock_); + Runtime* runtime = Runtime::Current(); + bool shutting_down = (runtime == NULL) || runtime->IsShuttingDown(); + PLOG(shutting_down ? WARNING : FATAL) << "pthread_mutex_destroy failed for " << name_; + } +#endif +} + +void Mutex::ExclusiveLock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + if (kDebugLocking && !recursive_) { + AssertNotHeld(self); + } + if (!recursive_ || !IsExclusiveHeld(self)) { +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (cur_state == 0) { + // Change state from 0 to 1. + done = android_atomic_acquire_cas(0, 1, &state_) == 0; + } else { + // Failed to acquire, hang up. + ScopedContentionRecorder scr(this, GetExclusiveOwnerTid(), SafeGetTid(self)); + android_atomic_inc(&num_contenders_); + if (futex(&state_, FUTEX_WAIT, 1, NULL, NULL, 0) != 0) { + if (errno != EAGAIN) { + PLOG(FATAL) << "futex wait failed for " << name_; + } + } + android_atomic_dec(&num_contenders_); + } + } while(!done); + DCHECK_EQ(state_, 1); + exclusive_owner_ = SafeGetTid(self); +#else + CHECK_MUTEX_CALL(pthread_mutex_lock, (&mutex_)); +#endif + RegisterAsLocked(self); + } + recursion_count_++; + if (kDebugLocking) { + CHECK(recursion_count_ == 1 || recursive_) << "Unexpected recursion count on mutex: " + << name_ << " " << recursion_count_; + AssertHeld(self); + } +} + +bool Mutex::ExclusiveTryLock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + if (kDebugLocking && !recursive_) { + AssertNotHeld(self); + } + if (!recursive_ || !IsExclusiveHeld(self)) { +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (cur_state == 0) { + // Change state from 0 to 1. + done = android_atomic_acquire_cas(0, 1, &state_) == 0; + } else { + return false; + } + } while(!done); + DCHECK_EQ(state_, 1); + exclusive_owner_ = SafeGetTid(self); +#else + int result = pthread_mutex_trylock(&mutex_); + if (result == EBUSY) { + return false; + } + if (result != 0) { + errno = result; + PLOG(FATAL) << "pthread_mutex_trylock failed for " << name_; + } +#endif + RegisterAsLocked(self); + } + recursion_count_++; + if (kDebugLocking) { + CHECK(recursion_count_ == 1 || recursive_) << "Unexpected recursion count on mutex: " + << name_ << " " << recursion_count_; + AssertHeld(self); + } + return true; +} + +void Mutex::ExclusiveUnlock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + AssertHeld(self); + recursion_count_--; + if (!recursive_ || recursion_count_ == 0) { + if (kDebugLocking) { + CHECK(recursion_count_ == 0 || recursive_) << "Unexpected recursion count on mutex: " + << name_ << " " << recursion_count_; + } + RegisterAsUnlocked(self); +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (cur_state == 1) { + // We're no longer the owner. + exclusive_owner_ = 0; + // Change state to 0. + done = android_atomic_release_cas(cur_state, 0, &state_) == 0; + if (done) { // Spurious fail? + // Wake a contender + if (num_contenders_ > 0) { + futex(&state_, FUTEX_WAKE, 1, NULL, NULL, 0); + } + } + } else { + // Logging acquires the logging lock, avoid infinite recursion in that case. + if (this != Locks::logging_lock_) { + LOG(FATAL) << "Unexpected state_ in unlock " << cur_state << " for " << name_; + } else { + LogMessageData data(__FILE__, __LINE__, INTERNAL_FATAL, -1); + LogMessage::LogLine(data, StringPrintf("Unexpected state_ %d in unlock for %s", + cur_state, name_).c_str()); + _exit(1); + } + } + } while(!done); +#else + CHECK_MUTEX_CALL(pthread_mutex_unlock, (&mutex_)); +#endif + } +} + +bool Mutex::IsExclusiveHeld(const Thread* self) const { + DCHECK(self == NULL || self == Thread::Current()); + bool result = (GetExclusiveOwnerTid() == SafeGetTid(self)); + if (kDebugLocking) { + // Sanity debug check that if we think it is locked we have it in our held mutexes. + if (result && self != NULL && level_ != kMonitorLock && !gAborting) { + CHECK_EQ(self->GetHeldMutex(level_), this); + } + } + return result; +} + +uint64_t Mutex::GetExclusiveOwnerTid() const { +#if ART_USE_FUTEXES + return exclusive_owner_; +#elif defined(__BIONIC__) + return static_cast((mutex_.value >> 16) & 0xffff); +#elif defined(__GLIBC__) + return reinterpret_cast(&mutex_)->owner; +#elif defined(__APPLE__) + const darwin_pthread_mutex_t* dpmutex = reinterpret_cast(&mutex_); + pthread_t owner = dpmutex->darwin_pthread_mutex_owner; + // 0 for unowned, -1 for PTHREAD_MTX_TID_SWITCHING + // TODO: should we make darwin_pthread_mutex_owner volatile and recheck until not -1? + if ((owner == (pthread_t)0) || (owner == (pthread_t)-1)) { + return 0; + } + uint64_t tid; + CHECK_PTHREAD_CALL(pthread_threadid_np, (owner, &tid), __FUNCTION__); // Requires Mac OS 10.6 + return tid; +#else +#error unsupported C library +#endif +} + +void Mutex::Dump(std::ostream& os) const { + os << (recursive_ ? "recursive " : "non-recursive ") + << name_ + << " level=" << static_cast(level_) + << " rec=" << recursion_count_ + << " owner=" << GetExclusiveOwnerTid() << " "; + DumpContention(os); +} + +std::ostream& operator<<(std::ostream& os, const Mutex& mu) { + mu.Dump(os); + return os; +} + +ReaderWriterMutex::ReaderWriterMutex(const char* name, LockLevel level) : + BaseMutex(name, level) +#if ART_USE_FUTEXES + , state_(0), exclusive_owner_(0), num_pending_readers_(0), num_pending_writers_(0) +#endif +{ +#if !ART_USE_FUTEXES + CHECK_MUTEX_CALL(pthread_rwlock_init, (&rwlock_, NULL)); +#endif +} + +ReaderWriterMutex::~ReaderWriterMutex() { +#if ART_USE_FUTEXES + CHECK_EQ(state_, 0); + CHECK_EQ(exclusive_owner_, 0U); + CHECK_EQ(num_pending_readers_, 0); + CHECK_EQ(num_pending_writers_, 0); +#else + // We can't use CHECK_MUTEX_CALL here because on shutdown a suspended daemon thread + // may still be using locks. + int rc = pthread_rwlock_destroy(&rwlock_); + if (rc != 0) { + errno = rc; + // TODO: should we just not log at all if shutting down? this could be the logging mutex! + MutexLock mu(Thread::Current(), *Locks::runtime_shutdown_lock_); + Runtime* runtime = Runtime::Current(); + bool shutting_down = runtime == NULL || runtime->IsShuttingDown(); + PLOG(shutting_down ? WARNING : FATAL) << "pthread_rwlock_destroy failed for " << name_; + } +#endif +} + +void ReaderWriterMutex::ExclusiveLock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + AssertNotExclusiveHeld(self); +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (cur_state == 0) { + // Change state from 0 to -1. + done = android_atomic_acquire_cas(0, -1, &state_) == 0; + } else { + // Failed to acquire, hang up. + ScopedContentionRecorder scr(this, GetExclusiveOwnerTid(), SafeGetTid(self)); + android_atomic_inc(&num_pending_writers_); + if (futex(&state_, FUTEX_WAIT, cur_state, NULL, NULL, 0) != 0) { + if (errno != EAGAIN) { + PLOG(FATAL) << "futex wait failed for " << name_; + } + } + android_atomic_dec(&num_pending_writers_); + } + } while(!done); + DCHECK_EQ(state_, -1); + exclusive_owner_ = SafeGetTid(self); +#else + CHECK_MUTEX_CALL(pthread_rwlock_wrlock, (&rwlock_)); +#endif + RegisterAsLocked(self); + AssertExclusiveHeld(self); +} + +void ReaderWriterMutex::ExclusiveUnlock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + AssertExclusiveHeld(self); + RegisterAsUnlocked(self); +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (cur_state == -1) { + // We're no longer the owner. + exclusive_owner_ = 0; + // Change state from -1 to 0. + done = android_atomic_release_cas(-1, 0, &state_) == 0; + if (done) { // cmpxchg may fail due to noise? + // Wake any waiters. + if (num_pending_readers_ > 0 || num_pending_writers_ > 0) { + futex(&state_, FUTEX_WAKE, -1, NULL, NULL, 0); + } + } + } else { + LOG(FATAL) << "Unexpected state_:" << cur_state << " for " << name_; + } + } while(!done); +#else + CHECK_MUTEX_CALL(pthread_rwlock_unlock, (&rwlock_)); +#endif +} + +#if HAVE_TIMED_RWLOCK +bool ReaderWriterMutex::ExclusiveLockWithTimeout(Thread* self, int64_t ms, int32_t ns) { + DCHECK(self == NULL || self == Thread::Current()); +#if ART_USE_FUTEXES + bool done = false; + timespec end_abs_ts; + InitTimeSpec(true, CLOCK_REALTIME, ms, ns, &end_abs_ts); + do { + int32_t cur_state = state_; + if (cur_state == 0) { + // Change state from 0 to -1. + done = android_atomic_acquire_cas(0, -1, &state_) == 0; + } else { + // Failed to acquire, hang up. + timespec now_abs_ts; + InitTimeSpec(true, CLOCK_REALTIME, 0, 0, &now_abs_ts); + timespec rel_ts; + if (ComputeRelativeTimeSpec(&rel_ts, end_abs_ts, now_abs_ts)) { + return false; // Timed out. + } + ScopedContentionRecorder scr(this, GetExclusiveOwnerTid(), SafeGetTid(self)); + android_atomic_inc(&num_pending_writers_); + if (futex(&state_, FUTEX_WAIT, cur_state, &rel_ts, NULL, 0) != 0) { + if (errno == ETIMEDOUT) { + android_atomic_dec(&num_pending_writers_); + return false; // Timed out. + } else if (errno != EAGAIN && errno != EINTR) { + PLOG(FATAL) << "timed futex wait failed for " << name_; + } + } + android_atomic_dec(&num_pending_writers_); + } + } while(!done); + exclusive_owner_ = SafeGetTid(self); +#else + timespec ts; + InitTimeSpec(true, CLOCK_REALTIME, ms, ns, &ts); + int result = pthread_rwlock_timedwrlock(&rwlock_, &ts); + if (result == ETIMEDOUT) { + return false; + } + if (result != 0) { + errno = result; + PLOG(FATAL) << "pthread_rwlock_timedwrlock failed for " << name_; + } +#endif + RegisterAsLocked(self); + AssertSharedHeld(self); + return true; +} +#endif + +bool ReaderWriterMutex::SharedTryLock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (cur_state >= 0) { + // Add as an extra reader. + done = android_atomic_acquire_cas(cur_state, cur_state + 1, &state_) == 0; + } else { + // Owner holds it exclusively. + return false; + } + } while(!done); +#else + int result = pthread_rwlock_tryrdlock(&rwlock_); + if (result == EBUSY) { + return false; + } + if (result != 0) { + errno = result; + PLOG(FATAL) << "pthread_mutex_trylock failed for " << name_; + } +#endif + RegisterAsLocked(self); + AssertSharedHeld(self); + return true; +} + +bool ReaderWriterMutex::IsExclusiveHeld(const Thread* self) const { + DCHECK(self == NULL || self == Thread::Current()); + bool result = (GetExclusiveOwnerTid() == SafeGetTid(self)); + if (kDebugLocking) { + // Sanity that if the pthread thinks we own the lock the Thread agrees. + if (self != NULL && result) { + CHECK_EQ(self->GetHeldMutex(level_), this); + } + } + return result; +} + +bool ReaderWriterMutex::IsSharedHeld(const Thread* self) const { + DCHECK(self == NULL || self == Thread::Current()); + bool result; + if (UNLIKELY(self == NULL)) { // Handle unattached threads. + result = IsExclusiveHeld(self); // TODO: a better best effort here. + } else { + result = (self->GetHeldMutex(level_) == this); + } + return result; +} + +uint64_t ReaderWriterMutex::GetExclusiveOwnerTid() const { +#if ART_USE_FUTEXES + int32_t state = state_; + if (state == 0) { + return 0; // No owner. + } else if (state > 0) { + return -1; // Shared. + } else { + return exclusive_owner_; + } +#else +#if defined(__BIONIC__) + return rwlock_.writerThreadId; +#elif defined(__GLIBC__) + return reinterpret_cast(&rwlock_)->writer; +#elif defined(__APPLE__) + const darwin_pthread_rwlock_t* + dprwlock = reinterpret_cast(&rwlock_); + pthread_t owner = dprwlock->darwin_pthread_rwlock_owner; + if (owner == (pthread_t)0) { + return 0; + } + uint64_t tid; + CHECK_PTHREAD_CALL(pthread_threadid_np, (owner, &tid), __FUNCTION__); // Requires Mac OS 10.6 + return tid; +#else +#error unsupported C library +#endif +#endif +} + +void ReaderWriterMutex::Dump(std::ostream& os) const { + os << name_ + << " level=" << static_cast(level_) + << " owner=" << GetExclusiveOwnerTid() << " "; + DumpContention(os); +} + +std::ostream& operator<<(std::ostream& os, const ReaderWriterMutex& mu) { + mu.Dump(os); + return os; +} + +ConditionVariable::ConditionVariable(const char* name, Mutex& guard) + : name_(name), guard_(guard) { +#if ART_USE_FUTEXES + sequence_ = 0; + num_waiters_ = 0; +#else + CHECK_MUTEX_CALL(pthread_cond_init, (&cond_, NULL)); +#endif +} + +ConditionVariable::~ConditionVariable() { +#if ART_USE_FUTEXES + if (num_waiters_!= 0) { + MutexLock mu(Thread::Current(), *Locks::runtime_shutdown_lock_); + Runtime* runtime = Runtime::Current(); + bool shutting_down = (runtime == NULL) || runtime->IsShuttingDown(); + LOG(shutting_down ? WARNING : FATAL) << "ConditionVariable::~ConditionVariable for " << name_ + << " called with " << num_waiters_ << " waiters."; + } +#else + // We can't use CHECK_MUTEX_CALL here because on shutdown a suspended daemon thread + // may still be using condition variables. + int rc = pthread_cond_destroy(&cond_); + if (rc != 0) { + errno = rc; + MutexLock mu(Thread::Current(), *Locks::runtime_shutdown_lock_); + Runtime* runtime = Runtime::Current(); + bool shutting_down = (runtime == NULL) || runtime->IsShuttingDown(); + PLOG(shutting_down ? WARNING : FATAL) << "pthread_cond_destroy failed for " << name_; + } +#endif +} + +void ConditionVariable::Broadcast(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + // TODO: enable below, there's a race in thread creation that causes false failures currently. + // guard_.AssertExclusiveHeld(self); + DCHECK_EQ(guard_.GetExclusiveOwnerTid(), SafeGetTid(self)); +#if ART_USE_FUTEXES + if (num_waiters_ > 0) { + android_atomic_inc(&sequence_); // Indicate the broadcast occurred. + bool done = false; + do { + int32_t cur_sequence = sequence_; + // Requeue waiters onto mutex. The waiter holds the contender count on the mutex high ensuring + // mutex unlocks will awaken the requeued waiter thread. + done = futex(&sequence_, FUTEX_CMP_REQUEUE, 0, + reinterpret_cast(std::numeric_limits::max()), + &guard_.state_, cur_sequence) != -1; + if (!done) { + if (errno != EAGAIN) { + PLOG(FATAL) << "futex cmp requeue failed for " << name_; + } + } + } while (!done); + } +#else + CHECK_MUTEX_CALL(pthread_cond_broadcast, (&cond_)); +#endif +} + +void ConditionVariable::Signal(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + guard_.AssertExclusiveHeld(self); +#if ART_USE_FUTEXES + if (num_waiters_ > 0) { + android_atomic_inc(&sequence_); // Indicate a signal occurred. + // Futex wake 1 waiter who will then come and in contend on mutex. It'd be nice to requeue them + // to avoid this, however, requeueing can only move all waiters. + int num_woken = futex(&sequence_, FUTEX_WAKE, 1, NULL, NULL, 0); + // Check something was woken or else we changed sequence_ before they had chance to wait. + CHECK((num_woken == 0) || (num_woken == 1)); + } +#else + CHECK_MUTEX_CALL(pthread_cond_signal, (&cond_)); +#endif +} + +void ConditionVariable::Wait(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + guard_.AssertExclusiveHeld(self); + unsigned int old_recursion_count = guard_.recursion_count_; +#if ART_USE_FUTEXES + num_waiters_++; + // Ensure the Mutex is contended so that requeued threads are awoken. + android_atomic_inc(&guard_.num_contenders_); + guard_.recursion_count_ = 1; + int32_t cur_sequence = sequence_; + guard_.ExclusiveUnlock(self); + if (futex(&sequence_, FUTEX_WAIT, cur_sequence, NULL, NULL, 0) != 0) { + // Futex failed, check it is an expected error. + // EAGAIN == EWOULDBLK, so we let the caller try again. + // EINTR implies a signal was sent to this thread. + if ((errno != EINTR) && (errno != EAGAIN)) { + PLOG(FATAL) << "futex wait failed for " << name_; + } + } + guard_.ExclusiveLock(self); + CHECK_GE(num_waiters_, 0); + num_waiters_--; + // We awoke and so no longer require awakes from the guard_'s unlock. + CHECK_GE(guard_.num_contenders_, 0); + android_atomic_dec(&guard_.num_contenders_); +#else + guard_.recursion_count_ = 0; + CHECK_MUTEX_CALL(pthread_cond_wait, (&cond_, &guard_.mutex_)); +#endif + guard_.recursion_count_ = old_recursion_count; +} + +void ConditionVariable::TimedWait(Thread* self, int64_t ms, int32_t ns) { + DCHECK(self == NULL || self == Thread::Current()); + guard_.AssertExclusiveHeld(self); + unsigned int old_recursion_count = guard_.recursion_count_; +#if ART_USE_FUTEXES + timespec rel_ts; + InitTimeSpec(false, CLOCK_REALTIME, ms, ns, &rel_ts); + num_waiters_++; + // Ensure the Mutex is contended so that requeued threads are awoken. + android_atomic_inc(&guard_.num_contenders_); + guard_.recursion_count_ = 1; + int32_t cur_sequence = sequence_; + guard_.ExclusiveUnlock(self); + if (futex(&sequence_, FUTEX_WAIT, cur_sequence, &rel_ts, NULL, 0) != 0) { + if (errno == ETIMEDOUT) { + // Timed out we're done. + } else if ((errno == EINTR) || (errno == EAGAIN)) { + // A signal or ConditionVariable::Signal/Broadcast has come in. + } else { + PLOG(FATAL) << "timed futex wait failed for " << name_; + } + } + guard_.ExclusiveLock(self); + CHECK_GE(num_waiters_, 0); + num_waiters_--; + // We awoke and so no longer require awakes from the guard_'s unlock. + CHECK_GE(guard_.num_contenders_, 0); + android_atomic_dec(&guard_.num_contenders_); +#else +#ifdef HAVE_TIMEDWAIT_MONOTONIC +#define TIMEDWAIT pthread_cond_timedwait_monotonic + int clock = CLOCK_MONOTONIC; +#else +#define TIMEDWAIT pthread_cond_timedwait + int clock = CLOCK_REALTIME; +#endif + guard_.recursion_count_ = 0; + timespec ts; + InitTimeSpec(true, clock, ms, ns, &ts); + int rc = TEMP_FAILURE_RETRY(TIMEDWAIT(&cond_, &guard_.mutex_, &ts)); + if (rc != 0 && rc != ETIMEDOUT) { + errno = rc; + PLOG(FATAL) << "TimedWait failed for " << name_; + } +#endif + guard_.recursion_count_ = old_recursion_count; +} + +} // namespace art diff --git a/src/base/mutex.h b/src/base/mutex.h new file mode 100644 index 0000000000000000000000000000000000000000..a3efd5c6f41d06b52351fddfababd42e0e19ee6c --- /dev/null +++ b/src/base/mutex.h @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2011 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_SRC_BASE_MUTEX_H_ +#define ART_SRC_BASE_MUTEX_H_ + +#include +#include + +#include +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "globals.h" +#include "locks.h" + +#if defined(__APPLE__) +#define ART_USE_FUTEXES 0 +#else +#define ART_USE_FUTEXES !defined(__mips__) +#endif + +// Currently Darwin doesn't support locks with timeouts. +#if !defined(__APPLE__) +#define HAVE_TIMED_RWLOCK 1 +#else +#define HAVE_TIMED_RWLOCK 0 +#endif + +// Record Log contention information, dumpable via SIGQUIT. +#define CONTENTION_LOGGING (0 && ART_USE_FUTEXES) +const size_t kContentionLogSize = 64; +#if CONTENTION_LOGGING +#include "atomic_integer.h" +#endif + +namespace art { + +class ScopedContentionRecorder; +class Thread; + +const bool kDebugLocking = kIsDebugBuild; + +// Base class for all Mutex implementations +class BaseMutex { + public: + const char* GetName() const { + return name_; + } + + virtual bool IsMutex() const { return false; } + virtual bool IsReaderWriterMutex() const { return false; } + + virtual void Dump(std::ostream& os) const = 0; + + static void DumpAll(std::ostream& os); + + protected: + friend class ConditionVariable; + + BaseMutex(const char* name, LockLevel level); + virtual ~BaseMutex(); + void RegisterAsLocked(Thread* self); + void RegisterAsUnlocked(Thread* self); + void CheckSafeToWait(Thread* self); + + friend class ScopedContentionRecorder; + + void RecordContention(uint64_t blocked_tid, uint64_t owner_tid, uint64_t milli_time_blocked); + void DumpContention(std::ostream& os) const; + + const LockLevel level_; // Support for lock hierarchy. + const char* const name_; +#if CONTENTION_LOGGING + // A log entry that records contention but makes no guarantee that either tid will be held live. + struct ContentionLogEntry { + ContentionLogEntry() : blocked_tid(0), owner_tid(0) {} + uint64_t blocked_tid; + uint64_t owner_tid; + AtomicInteger count; + }; + ContentionLogEntry contention_log_[kContentionLogSize]; + // The next entry in the contention log to be updated. Value ranges from 0 to + // kContentionLogSize - 1. + AtomicInteger cur_content_log_entry_; + // Number of times the Mutex has been contended. + AtomicInteger contention_count_; + // Sum of time waited by all contenders in ms. + AtomicInteger wait_time_; +#endif +}; + +// A Mutex is used to achieve mutual exclusion between threads. A Mutex can be used to gain +// exclusive access to what it guards. A Mutex can be in one of two states: +// - Free - not owned by any thread, +// - Exclusive - owned by a single thread. +// +// The effect of locking and unlocking operations on the state is: +// State | ExclusiveLock | ExclusiveUnlock +// ------------------------------------------- +// Free | Exclusive | error +// Exclusive | Block* | Free +// * Mutex is not reentrant and so an attempt to ExclusiveLock on the same thread will result in +// an error. Being non-reentrant simplifies Waiting on ConditionVariables. +std::ostream& operator<<(std::ostream& os, const Mutex& mu); +class LOCKABLE Mutex : public BaseMutex { + public: + explicit Mutex(const char* name, LockLevel level = kDefaultMutexLevel, bool recursive = false); + ~Mutex(); + + virtual bool IsMutex() const { return true; } + + // Block until mutex is free then acquire exclusive access. + void ExclusiveLock(Thread* self) EXCLUSIVE_LOCK_FUNCTION(); + void Lock(Thread* self) EXCLUSIVE_LOCK_FUNCTION() { ExclusiveLock(self); } + + // Returns true if acquires exclusive access, false otherwise. + bool ExclusiveTryLock(Thread* self) EXCLUSIVE_TRYLOCK_FUNCTION(true); + bool TryLock(Thread* self) EXCLUSIVE_TRYLOCK_FUNCTION(true) { return ExclusiveTryLock(self); } + + // Release exclusive access. + void ExclusiveUnlock(Thread* self) UNLOCK_FUNCTION(); + void Unlock(Thread* self) UNLOCK_FUNCTION() { ExclusiveUnlock(self); } + + // Is the current thread the exclusive holder of the Mutex. + bool IsExclusiveHeld(const Thread* self) const; + + // Assert that the Mutex is exclusively held by the current thread. + void AssertExclusiveHeld(const Thread* self) { + if (kDebugLocking && !gAborting) { + CHECK(IsExclusiveHeld(self)) << *this; + } + } + void AssertHeld(const Thread* self) { AssertExclusiveHeld(self); } + + // Assert that the Mutex is not held by the current thread. + void AssertNotHeldExclusive(const Thread* self) { + if (kDebugLocking) { + CHECK(!IsExclusiveHeld(self)) << *this; + } + } + void AssertNotHeld(const Thread* self) { AssertNotHeldExclusive(self); } + + // Id associated with exclusive owner. + uint64_t GetExclusiveOwnerTid() const; + + // Returns how many times this Mutex has been locked, it is better to use AssertHeld/NotHeld. + unsigned int GetDepth() const { + return recursion_count_; + } + + virtual void Dump(std::ostream& os) const; + + private: +#if ART_USE_FUTEXES + // 0 is unheld, 1 is held. + volatile int32_t state_; + // Exclusive owner. + volatile uint64_t exclusive_owner_; + // Number of waiting contenders. + volatile int32_t num_contenders_; +#else + pthread_mutex_t mutex_; +#endif + const bool recursive_; // Can the lock be recursively held? + unsigned int recursion_count_; + friend class ConditionVariable; + friend class MutexTester; + DISALLOW_COPY_AND_ASSIGN(Mutex); +}; + +// A ReaderWriterMutex is used to achieve mutual exclusion between threads, similar to a Mutex. +// Unlike a Mutex a ReaderWriterMutex can be used to gain exclusive (writer) or shared (reader) +// access to what it guards. A flaw in relation to a Mutex is that it cannot be used with a +// condition variable. A ReaderWriterMutex can be in one of three states: +// - Free - not owned by any thread, +// - Exclusive - owned by a single thread, +// - Shared(n) - shared amongst n threads. +// +// The effect of locking and unlocking operations on the state is: +// +// State | ExclusiveLock | ExclusiveUnlock | SharedLock | SharedUnlock +// ---------------------------------------------------------------------------- +// Free | Exclusive | error | SharedLock(1) | error +// Exclusive | Block | Free | Block | error +// Shared(n) | Block | error | SharedLock(n+1)* | Shared(n-1) or Free +// * for large values of n the SharedLock may block. +std::ostream& operator<<(std::ostream& os, const ReaderWriterMutex& mu); +class LOCKABLE ReaderWriterMutex : public BaseMutex { + public: + explicit ReaderWriterMutex(const char* name, LockLevel level = kDefaultMutexLevel); + ~ReaderWriterMutex(); + + virtual bool IsReaderWriterMutex() const { return true; } + + // Block until ReaderWriterMutex is free then acquire exclusive access. + void ExclusiveLock(Thread* self) EXCLUSIVE_LOCK_FUNCTION(); + void WriterLock(Thread* self) EXCLUSIVE_LOCK_FUNCTION() { ExclusiveLock(self); } + + // Release exclusive access. + void ExclusiveUnlock(Thread* self) UNLOCK_FUNCTION(); + void WriterUnlock(Thread* self) UNLOCK_FUNCTION() { ExclusiveUnlock(self); } + + // Block until ReaderWriterMutex is free and acquire exclusive access. Returns true on success + // or false if timeout is reached. +#if HAVE_TIMED_RWLOCK + bool ExclusiveLockWithTimeout(Thread* self, int64_t ms, int32_t ns) + EXCLUSIVE_TRYLOCK_FUNCTION(true); +#endif + + // Block until ReaderWriterMutex is shared or free then acquire a share on the access. + void SharedLock(Thread* self) SHARED_LOCK_FUNCTION() ALWAYS_INLINE; + void ReaderLock(Thread* self) SHARED_LOCK_FUNCTION() { SharedLock(self); } + + // Try to acquire share of ReaderWriterMutex. + bool SharedTryLock(Thread* self) EXCLUSIVE_TRYLOCK_FUNCTION(true); + + // Release a share of the access. + void SharedUnlock(Thread* self) UNLOCK_FUNCTION() ALWAYS_INLINE; + void ReaderUnlock(Thread* self) UNLOCK_FUNCTION() { SharedUnlock(self); } + + // Is the current thread the exclusive holder of the ReaderWriterMutex. + bool IsExclusiveHeld(const Thread* self) const; + + // Assert the current thread has exclusive access to the ReaderWriterMutex. + void AssertExclusiveHeld(const Thread* self) { + if (kDebugLocking) { + CHECK(IsExclusiveHeld(self)) << *this; + } + } + void AssertWriterHeld(const Thread* self) { AssertExclusiveHeld(self); } + + // Assert the current thread doesn't have exclusive access to the ReaderWriterMutex. + void AssertNotExclusiveHeld(const Thread* self) { + if (kDebugLocking) { + CHECK(!IsExclusiveHeld(self)); + } + } + void AssertNotWriterHeld(const Thread* self) { AssertNotExclusiveHeld(self); } + + // Is the current thread a shared holder of the ReaderWriterMutex. + bool IsSharedHeld(const Thread* self) const; + + // Assert the current thread has shared access to the ReaderWriterMutex. + void AssertSharedHeld(const Thread* self) { + if (kDebugLocking) { + // TODO: we can only assert this well when self != NULL. + CHECK(IsSharedHeld(self) || self == NULL) << *this; + } + } + void AssertReaderHeld(const Thread* self) { AssertSharedHeld(self); } + + // Assert the current thread doesn't hold this ReaderWriterMutex either in shared or exclusive + // mode. + void AssertNotHeld(const Thread* self) { + if (kDebugLocking) { + CHECK(!IsSharedHeld(self)) << *this; + } + } + + // Id associated with exclusive owner. + uint64_t GetExclusiveOwnerTid() const; + + virtual void Dump(std::ostream& os) const; + + private: +#if ART_USE_FUTEXES + // -1 implies held exclusive, +ve shared held by state_ many owners. + volatile int32_t state_; + // Exclusive owner. + volatile uint64_t exclusive_owner_; + // Pending readers. + volatile int32_t num_pending_readers_; + // Pending writers. + volatile int32_t num_pending_writers_; +#else + pthread_rwlock_t rwlock_; +#endif + friend class MutexTester; + DISALLOW_COPY_AND_ASSIGN(ReaderWriterMutex); +}; + +// ConditionVariables allow threads to queue and sleep. Threads may then be resumed individually +// (Signal) or all at once (Broadcast). +class ConditionVariable { + public: + explicit ConditionVariable(const char* name, Mutex& mutex); + ~ConditionVariable(); + + void Broadcast(Thread* self); + void Signal(Thread* self); + // TODO: No thread safety analysis on Wait and TimedWait as they call mutex operations via their + // pointer copy, thereby defeating annotalysis. + void Wait(Thread* self) NO_THREAD_SAFETY_ANALYSIS; + void TimedWait(Thread* self, int64_t ms, int32_t ns) NO_THREAD_SAFETY_ANALYSIS; + + private: + const char* const name_; + // The Mutex being used by waiters. It is an error to mix condition variables between different + // Mutexes. + Mutex& guard_; +#if ART_USE_FUTEXES + // A counter that is modified by signals and broadcasts. This ensures that when a waiter gives up + // their Mutex and another thread takes it and signals, the waiting thread observes that sequence_ + // changed and doesn't enter the wait. Modified while holding guard_, but is read by futex wait + // without guard_ held. + volatile int32_t sequence_; + // Number of threads that have come into to wait, not the length of the waiters on the futex as + // waiters may have been requeued onto guard_. Guarded by guard_. + volatile int32_t num_waiters_; +#else + pthread_cond_t cond_; +#endif + DISALLOW_COPY_AND_ASSIGN(ConditionVariable); +}; + +// Scoped locker/unlocker for a regular Mutex that acquires mu upon construction and releases it +// upon destruction. +class SCOPED_LOCKABLE MutexLock { + public: + explicit MutexLock(Thread* self, Mutex& mu) EXCLUSIVE_LOCK_FUNCTION(mu) : self_(self), mu_(mu) { + mu_.ExclusiveLock(self_); + } + + ~MutexLock() UNLOCK_FUNCTION() { + mu_.ExclusiveUnlock(self_); + } + + private: + Thread* const self_; + Mutex& mu_; + DISALLOW_COPY_AND_ASSIGN(MutexLock); +}; +// Catch bug where variable name is omitted. "MutexLock (lock);" instead of "MutexLock mu(lock)". +#define MutexLock(x) COMPILE_ASSERT(0, mutex_lock_declaration_missing_variable_name) + +// Scoped locker/unlocker for a ReaderWriterMutex that acquires read access to mu upon +// construction and releases it upon destruction. +class SCOPED_LOCKABLE ReaderMutexLock { + public: + explicit ReaderMutexLock(Thread* self, ReaderWriterMutex& mu) EXCLUSIVE_LOCK_FUNCTION(mu) : + self_(self), mu_(mu) { + mu_.SharedLock(self_); + } + + ~ReaderMutexLock() UNLOCK_FUNCTION() { + mu_.SharedUnlock(self_); + } + + private: + Thread* const self_; + ReaderWriterMutex& mu_; + DISALLOW_COPY_AND_ASSIGN(ReaderMutexLock); +}; +// Catch bug where variable name is omitted. "ReaderMutexLock (lock);" instead of +// "ReaderMutexLock mu(lock)". +#define ReaderMutexLock(x) COMPILE_ASSERT(0, reader_mutex_lock_declaration_missing_variable_name) + +// Scoped locker/unlocker for a ReaderWriterMutex that acquires write access to mu upon +// construction and releases it upon destruction. +class SCOPED_LOCKABLE WriterMutexLock { + public: + explicit WriterMutexLock(Thread* self, ReaderWriterMutex& mu) EXCLUSIVE_LOCK_FUNCTION(mu) : + self_(self), mu_(mu) { + mu_.ExclusiveLock(self_); + } + + ~WriterMutexLock() UNLOCK_FUNCTION() { + mu_.ExclusiveUnlock(self_); + } + + private: + Thread* const self_; + ReaderWriterMutex& mu_; + DISALLOW_COPY_AND_ASSIGN(WriterMutexLock); +}; +// Catch bug where variable name is omitted. "WriterMutexLock (lock);" instead of +// "WriterMutexLock mu(lock)". +#define WriterMutexLock(x) COMPILE_ASSERT(0, writer_mutex_lock_declaration_missing_variable_name) + +} // namespace art + +#endif // ART_SRC_BASE_MUTEX_H_ diff --git a/src/base/mutex_test.cc b/src/base/mutex_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..1af8e0ab991a5f2e69debcb688e369c38ac8adc8 --- /dev/null +++ b/src/base/mutex_test.cc @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mutex.h" + +#include "common_test.h" + +namespace art { + +class MutexTest : public CommonTest {}; + +struct MutexTester { + static void AssertDepth(Mutex& mu, uint32_t expected_depth) { + ASSERT_EQ(expected_depth, mu.GetDepth()); + + // This test is single-threaded, so we also know _who_ should hold the lock. + if (expected_depth == 0) { + mu.AssertNotHeld(Thread::Current()); + } else { + mu.AssertHeld(Thread::Current()); + } + } +}; + +TEST_F(MutexTest, LockUnlock) { + Mutex mu("test mutex"); + MutexTester::AssertDepth(mu, 0U); + mu.Lock(Thread::Current()); + MutexTester::AssertDepth(mu, 1U); + mu.Unlock(Thread::Current()); + MutexTester::AssertDepth(mu, 0U); +} + +// GCC has trouble with our mutex tests, so we have to turn off thread safety analysis. +static void TryLockUnlockTest() NO_THREAD_SAFETY_ANALYSIS { + Mutex mu("test mutex"); + MutexTester::AssertDepth(mu, 0U); + ASSERT_TRUE(mu.TryLock(Thread::Current())); + MutexTester::AssertDepth(mu, 1U); + mu.Unlock(Thread::Current()); + MutexTester::AssertDepth(mu, 0U); +} + +TEST_F(MutexTest, TryLockUnlock) { + TryLockUnlockTest(); +} + +// GCC has trouble with our mutex tests, so we have to turn off thread safety analysis. +static void RecursiveLockUnlockTest() NO_THREAD_SAFETY_ANALYSIS { + Mutex mu("test mutex", kDefaultMutexLevel, true); + MutexTester::AssertDepth(mu, 0U); + mu.Lock(Thread::Current()); + MutexTester::AssertDepth(mu, 1U); + mu.Lock(Thread::Current()); + MutexTester::AssertDepth(mu, 2U); + mu.Unlock(Thread::Current()); + MutexTester::AssertDepth(mu, 1U); + mu.Unlock(Thread::Current()); + MutexTester::AssertDepth(mu, 0U); +} + +TEST_F(MutexTest, RecursiveLockUnlock) { + RecursiveLockUnlockTest(); +} + +// GCC has trouble with our mutex tests, so we have to turn off thread safety analysis. +static void RecursiveTryLockUnlockTest() NO_THREAD_SAFETY_ANALYSIS { + Mutex mu("test mutex", kDefaultMutexLevel, true); + MutexTester::AssertDepth(mu, 0U); + ASSERT_TRUE(mu.TryLock(Thread::Current())); + MutexTester::AssertDepth(mu, 1U); + ASSERT_TRUE(mu.TryLock(Thread::Current())); + MutexTester::AssertDepth(mu, 2U); + mu.Unlock(Thread::Current()); + MutexTester::AssertDepth(mu, 1U); + mu.Unlock(Thread::Current()); + MutexTester::AssertDepth(mu, 0U); +} + +TEST_F(MutexTest, RecursiveTryLockUnlock) { + RecursiveTryLockUnlockTest(); +} + + +struct RecursiveLockWait { + explicit RecursiveLockWait() + : mu("test mutex", kDefaultMutexLevel, true), cv("test condition variable", mu) { + } + + static void* Callback(void* arg) { + RecursiveLockWait* state = reinterpret_cast(arg); + state->mu.Lock(Thread::Current()); + state->cv.Signal(Thread::Current()); + state->mu.Unlock(Thread::Current()); + return NULL; + } + + Mutex mu; + ConditionVariable cv; +}; + +// GCC has trouble with our mutex tests, so we have to turn off thread safety analysis. +static void RecursiveLockWaitTest() NO_THREAD_SAFETY_ANALYSIS { + RecursiveLockWait state; + state.mu.Lock(Thread::Current()); + state.mu.Lock(Thread::Current()); + + pthread_t pthread; + int pthread_create_result = pthread_create(&pthread, NULL, RecursiveLockWait::Callback, &state); + ASSERT_EQ(0, pthread_create_result); + + state.cv.Wait(Thread::Current()); + + state.mu.Unlock(Thread::Current()); + state.mu.Unlock(Thread::Current()); + EXPECT_EQ(pthread_join(pthread, NULL), 0); +} + +// This ensures we don't hang when waiting on a recursively locked mutex, +// which is not supported with bare pthread_mutex_t. +TEST_F(MutexTest, RecursiveLockWait) { + RecursiveLockWaitTest(); +} + +TEST_F(MutexTest, SharedLockUnlock) { + ReaderWriterMutex mu("test rwmutex"); + mu.AssertNotHeld(Thread::Current()); + mu.AssertNotExclusiveHeld(Thread::Current()); + mu.SharedLock(Thread::Current()); + mu.AssertSharedHeld(Thread::Current()); + mu.AssertNotExclusiveHeld(Thread::Current()); + mu.SharedUnlock(Thread::Current()); + mu.AssertNotHeld(Thread::Current()); +} + +TEST_F(MutexTest, ExclusiveLockUnlock) { + ReaderWriterMutex mu("test rwmutex"); + mu.AssertNotHeld(Thread::Current()); + mu.ExclusiveLock(Thread::Current()); + mu.AssertSharedHeld(Thread::Current()); + mu.AssertExclusiveHeld(Thread::Current()); + mu.ExclusiveUnlock(Thread::Current()); + mu.AssertNotHeld(Thread::Current()); +} + +// GCC has trouble with our mutex tests, so we have to turn off thread safety analysis. +static void SharedTryLockUnlockTest() NO_THREAD_SAFETY_ANALYSIS { + ReaderWriterMutex mu("test rwmutex"); + mu.AssertNotHeld(Thread::Current()); + ASSERT_TRUE(mu.SharedTryLock(Thread::Current())); + mu.AssertSharedHeld(Thread::Current()); + mu.SharedUnlock(Thread::Current()); + mu.AssertNotHeld(Thread::Current()); +} + +TEST_F(MutexTest, SharedTryLockUnlock) { + SharedTryLockUnlockTest(); +} + +} // namespace art diff --git a/src/base/stl_util.h b/src/base/stl_util.h new file mode 100644 index 0000000000000000000000000000000000000000..eb8be42df3d3d1b0fe9dbd2dd14d01c43888cd1a --- /dev/null +++ b/src/base/stl_util.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2011 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_SRC_BASE_STL_UTIL_H_ +#define ART_SRC_BASE_STL_UTIL_H_ + +#include +#include + +namespace art { + +// Sort and remove duplicates of an STL vector or deque. +template +void STLSortAndRemoveDuplicates(T* v) { + std::sort(v->begin(), v->end()); + v->erase(std::unique(v->begin(), v->end()), v->end()); +} + +// STLDeleteContainerPointers() +// For a range within a container of pointers, calls delete +// (non-array version) on these pointers. +// NOTE: for these three functions, we could just implement a DeleteObject +// functor and then call for_each() on the range and functor, but this +// requires us to pull in all of algorithm.h, which seems expensive. +// For hash_[multi]set, it is important that this deletes behind the iterator +// because the hash_set may call the hash function on the iterator when it is +// advanced, which could result in the hash function trying to deference a +// stale pointer. +template +void STLDeleteContainerPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete *temp; + } +} + +// STLDeleteElements() deletes all the elements in an STL container and clears +// the container. This function is suitable for use with a vector, set, +// hash_set, or any other STL container which defines sensible begin(), end(), +// and clear() methods. +// +// If container is NULL, this function is a no-op. +// +// As an alternative to calling STLDeleteElements() directly, consider +// ElementDeleter (defined below), which ensures that your container's elements +// are deleted when the ElementDeleter goes out of scope. +template +void STLDeleteElements(T *container) { + if (!container) return; + STLDeleteContainerPointers(container->begin(), container->end()); + container->clear(); +} + +// Given an STL container consisting of (key, value) pairs, STLDeleteValues +// deletes all the "value" components and clears the container. Does nothing +// in the case it's given a NULL pointer. +template +void STLDeleteValues(T *v) { + if (!v) return; + for (typename T::iterator i = v->begin(); i != v->end(); ++i) { + delete i->second; + } + v->clear(); +} + +template +std::string ToString(const T& v) { + std::ostringstream os; + os << "["; + for (size_t i = 0; i < v.size(); ++i) { + os << v[i]; + if (i < v.size() - 1) { + os << ", "; + } + } + os << "]"; + return os.str(); +} + +} // namespace art + +#endif // ART_SRC_BASE_STL_UTIL_H_ diff --git a/src/base/stringpiece.cc b/src/base/stringpiece.cc new file mode 100644 index 0000000000000000000000000000000000000000..715d964a12f5b5166a859862bbc87da7279395ad --- /dev/null +++ b/src/base/stringpiece.cc @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stringpiece.h" + +#include +#include + +namespace art { + +bool operator<(const StringPiece& x, const StringPiece& y) { + const int r = memcmp(x.data(), y.data(), + std::min(x.size(), y.size())); + return ((r < 0) || ((r == 0) && (x.size() < y.size()))); +} + +void StringPiece::CopyToString(std::string* target) const { + target->assign(ptr_, length_); +} + +int StringPiece::copy(char* buf, size_type n, size_type pos) const { + int ret = std::min(length_ - pos, n); + memcpy(buf, ptr_ + pos, ret); + return ret; +} + +StringPiece::size_type StringPiece::find(const StringPiece& s, size_type pos) const { + if (length_ < 0 || pos > static_cast(length_)) + return npos; + + const char* result = std::search(ptr_ + pos, ptr_ + length_, + s.ptr_, s.ptr_ + s.length_); + const size_type xpos = result - ptr_; + return xpos + s.length_ <= static_cast(length_) ? xpos : npos; +} + +int StringPiece::compare(const StringPiece& x) const { + int r = memcmp(ptr_, x.ptr_, std::min(length_, x.length_)); + if (r == 0) { + if (length_ < x.length_) r = -1; + else if (length_ > x.length_) r = +1; + } + return r; +} + +StringPiece::size_type StringPiece::find(char c, size_type pos) const { + if (length_ <= 0 || pos >= static_cast(length_)) { + return npos; + } + const char* result = std::find(ptr_ + pos, ptr_ + length_, c); + return result != ptr_ + length_ ? result - ptr_ : npos; +} + +StringPiece::size_type StringPiece::rfind(const StringPiece& s, size_type pos) const { + if (length_ < s.length_) return npos; + const size_t ulen = length_; + if (s.length_ == 0) return std::min(ulen, pos); + + const char* last = ptr_ + std::min(ulen - s.length_, pos) + s.length_; + const char* result = std::find_end(ptr_, last, s.ptr_, s.ptr_ + s.length_); + return result != last ? result - ptr_ : npos; +} + +StringPiece::size_type StringPiece::rfind(char c, size_type pos) const { + if (length_ <= 0) return npos; + for (int i = std::min(pos, static_cast(length_ - 1)); + i >= 0; --i) { + if (ptr_[i] == c) { + return i; + } + } + return npos; +} + +StringPiece StringPiece::substr(size_type pos, size_type n) const { + if (pos > static_cast(length_)) pos = length_; + if (n > length_ - pos) n = length_ - pos; + return StringPiece(ptr_ + pos, n); +} + +const StringPiece::size_type StringPiece::npos = size_type(-1); + +std::ostream& operator<<(std::ostream& o, const StringPiece& piece) { + o.write(piece.data(), piece.size()); + return o; +} + +} // namespace art diff --git a/src/base/stringpiece.h b/src/base/stringpiece.h new file mode 100644 index 0000000000000000000000000000000000000000..193f5f7e7b9d8d770340eaddf8e829331c9c6305 --- /dev/null +++ b/src/base/stringpiece.h @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2010 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. + */ + +// A string-like object that points to a sized piece of memory. +// +// Functions or methods may use const StringPiece& parameters to accept either +// a "const char*" or a "string" value that will be implicitly converted to +// a StringPiece. The implicit conversion means that it is often appropriate +// to include this .h file in other files rather than forward-declaring +// StringPiece as would be appropriate for most other Google classes. +// +// Systematic usage of StringPiece is encouraged as it will reduce unnecessary +// conversions from "const char*" to "string" and back again. + +#ifndef ART_SRC_BASE_STRINGPIECE_H_ +#define ART_SRC_BASE_STRINGPIECE_H_ + +#include +#include +#include +#include +#include + +namespace art { + +class StringPiece { + private: + const char* ptr_; + int length_; + + public: + // We provide non-explicit singleton constructors so users can pass + // in a "const char*" or a "string" wherever a "StringPiece" is + // expected. + StringPiece() : ptr_(NULL), length_(0) { } + StringPiece(const char* str) // NOLINT + : ptr_(str), length_((str == NULL) ? 0 : static_cast(strlen(str))) { } + StringPiece(const std::string& str) // NOLINT + : ptr_(str.data()), length_(static_cast(str.size())) { } + StringPiece(const char* offset, int len) : ptr_(offset), length_(len) { } + + // data() may return a pointer to a buffer with embedded NULs, and the + // returned buffer may or may not be null terminated. Therefore it is + // typically a mistake to pass data() to a routine that expects a NUL + // terminated string. + const char* data() const { return ptr_; } + int size() const { return length_; } + int length() const { return length_; } + bool empty() const { return length_ == 0; } + + void clear() { + ptr_ = NULL; + length_ = 0; + } + void set(const char* data, int len) { + ptr_ = data; + length_ = len; + } + void set(const char* str) { + ptr_ = str; + if (str != NULL) + length_ = static_cast(strlen(str)); + else + length_ = 0; + } + void set(const void* data, int len) { + ptr_ = reinterpret_cast(data); + length_ = len; + } + + char operator[](int i) const { return ptr_[i]; } + + void remove_prefix(int n) { + ptr_ += n; + length_ -= n; + } + + void remove_suffix(int n) { + length_ -= n; + } + + int compare(const StringPiece& x) const; + + std::string as_string() const { + return std::string(data(), size()); + } + // We also define ToString() here, since many other string-like + // interfaces name the routine that converts to a C++ string + // "ToString", and it's confusing to have the method that does that + // for a StringPiece be called "as_string()". We also leave the + // "as_string()" method defined here for existing code. + std::string ToString() const { + return std::string(data(), size()); + } + + void CopyToString(std::string* target) const; + void AppendToString(std::string* target) const; + + // Does "this" start with "x" + bool starts_with(const StringPiece& x) const { + return ((length_ >= x.length_) && + (memcmp(ptr_, x.ptr_, x.length_) == 0)); + } + + // Does "this" end with "x" + bool ends_with(const StringPiece& x) const { + return ((length_ >= x.length_) && + (memcmp(ptr_ + (length_-x.length_), x.ptr_, x.length_) == 0)); + } + + // standard STL container boilerplate + typedef char value_type; + typedef const char* pointer; + typedef const char& reference; + typedef const char& const_reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + static const size_type npos; + typedef const char* const_iterator; + typedef const char* iterator; + typedef std::reverse_iterator const_reverse_iterator; + typedef std::reverse_iterator reverse_iterator; + iterator begin() const { return ptr_; } + iterator end() const { return ptr_ + length_; } + const_reverse_iterator rbegin() const { + return const_reverse_iterator(ptr_ + length_); + } + const_reverse_iterator rend() const { + return const_reverse_iterator(ptr_); + } + // STLS says return size_type, but Google says return int + int max_size() const { return length_; } + int capacity() const { return length_; } + + int copy(char* buf, size_type n, size_type pos = 0) const; + + size_type find(const StringPiece& s, size_type pos = 0) const; + size_type find(char c, size_type pos = 0) const; + size_type rfind(const StringPiece& s, size_type pos = npos) const; + size_type rfind(char c, size_type pos = npos) const; + + StringPiece substr(size_type pos, size_type n = npos) const; +}; + +// This large function is defined inline so that in a fairly common case where +// one of the arguments is a literal, the compiler can elide a lot of the +// following comparisons. +inline bool operator==(const StringPiece& x, const StringPiece& y) { + int len = x.size(); + if (len != y.size()) { + return false; + } + + const char* p1 = x.data(); + const char* p2 = y.data(); + if (p1 == p2) { + return true; + } + if (len <= 0) { + return true; + } + + // Test last byte in case strings share large common prefix + if (p1[len-1] != p2[len-1]) return false; + if (len == 1) return true; + + // At this point we can, but don't have to, ignore the last byte. We use + // this observation to fold the odd-length case into the even-length case. + len &= ~1; + + return memcmp(p1, p2, len) == 0; +} + +inline bool operator!=(const StringPiece& x, const StringPiece& y) { + return !(x == y); +} + +bool operator<(const StringPiece& x, const StringPiece& y); + +inline bool operator>(const StringPiece& x, const StringPiece& y) { + return y < x; +} + +inline bool operator<=(const StringPiece& x, const StringPiece& y) { + return !(x > y); +} + +inline bool operator>=(const StringPiece& x, const StringPiece& y) { + return !(x < y); +} + +extern std::ostream& operator<<(std::ostream& o, const StringPiece& piece); + +struct StringPieceHash { + size_t operator()(const StringPiece& string_piece) const { + size_t string_size = string_piece.size(); + const char* string_data = string_piece.data(); + // This is the java.lang.String hashcode for convenience, not interoperability. + size_t hash = 0; + while (string_size--) { + hash = hash * 31 + *string_data++; + } + return hash; + } +}; + +} // namespace art + +#endif // ART_SRC_BASE_STRINGPIECE_H_ diff --git a/src/base/stringprintf.cc b/src/base/stringprintf.cc new file mode 100644 index 0000000000000000000000000000000000000000..8fd9257048e3a4f90dd2339b6c50e94a36bf2cce --- /dev/null +++ b/src/base/stringprintf.cc @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stringprintf.h" + +#include + +namespace art { + +void StringAppendV(std::string* dst, const char* format, va_list ap) { + // First try with a small fixed size buffer + char space[1024]; + + // It's possible for methods that use a va_list to invalidate + // the data in it upon use. The fix is to make a copy + // of the structure before using it and use that copy instead. + va_list backup_ap; + va_copy(backup_ap, ap); + int result = vsnprintf(space, sizeof(space), format, backup_ap); + va_end(backup_ap); + + if (result < static_cast(sizeof(space))) { + if (result >= 0) { + // Normal case -- everything fit. + dst->append(space, result); + return; + } + + if (result < 0) { + // Just an error. + return; + } + } + + // Increase the buffer size to the size requested by vsnprintf, + // plus one for the closing \0. + int length = result+1; + char* buf = new char[length]; + + // Restore the va_list before we use it again + va_copy(backup_ap, ap); + result = vsnprintf(buf, length, format, backup_ap); + va_end(backup_ap); + + if (result >= 0 && result < length) { + // It fit + dst->append(buf, result); + } + delete[] buf; +} + +std::string StringPrintf(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string result; + StringAppendV(&result, fmt, ap); + va_end(ap); + return result; +} + +void StringAppendF(std::string* dst, const char* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(dst, format, ap); + va_end(ap); +} + +} // namespace art diff --git a/src/base/stringprintf.h b/src/base/stringprintf.h new file mode 100644 index 0000000000000000000000000000000000000000..d707cc02d659da9af53f70514b53be40f22972a8 --- /dev/null +++ b/src/base/stringprintf.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 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_SRC_BASE_STRINGPRINTF_H_ +#define ART_SRC_BASE_STRINGPRINTF_H_ + +#include +#include + +namespace art { + +// Returns a string corresponding to printf-like formatting of the arguments. +std::string StringPrintf(const char* fmt, ...) + __attribute__((__format__(__printf__, 1, 2))); + +// Appends a printf-like formatting of the arguments to 'dst'. +void StringAppendF(std::string* dst, const char* fmt, ...) + __attribute__((__format__(__printf__, 2, 3))); + +// Appends a printf-like formatting of the arguments to 'dst'. +void StringAppendV(std::string* dst, const char* format, va_list ap); + +} // namespace art + +#endif // ART_SRC_BASE_STRINGPRINTF_H_ diff --git a/src/base/timing_logger.cc b/src/base/timing_logger.cc new file mode 100644 index 0000000000000000000000000000000000000000..6d5586c08f81a0afa101efef0901d382e658c9e5 --- /dev/null +++ b/src/base/timing_logger.cc @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "timing_logger.h" + +#include "base/logging.h" +#include "thread.h" +#include "base/stl_util.h" +#include "base/histogram-inl.h" + +#include +#include + +namespace art { + +void TimingLogger::Reset() { + times_.clear(); + labels_.clear(); + AddSplit(""); +} + +TimingLogger::TimingLogger(const std::string &name, bool precise) + : name_(name), + precise_(precise) { + AddSplit(""); +} + +void TimingLogger::AddSplit(const std::string &label) { + times_.push_back(NanoTime()); + labels_.push_back(label); +} + +uint64_t TimingLogger::GetTotalNs() const { + return times_.back() - times_.front(); +} +; + +void TimingLogger::Dump(std::ostream &os) const { + uint64_t largest_time = 0; + os << name_ << ": begin\n"; + for (size_t i = 1; i < times_.size(); ++i) { + uint64_t delta_time = times_[i] - times_[i - 1]; + largest_time = std::max(largest_time, delta_time); + } + // Compute which type of unit we will use for printing the timings. + TimeUnit tu = GetAppropriateTimeUnit(largest_time); + uint64_t divisor = GetNsToTimeUnitDivisor(tu); + for (size_t i = 1; i < times_.size(); ++i) { + uint64_t delta_time = times_[i] - times_[i - 1]; + if (!precise_ && divisor >= 1000) { + // Make the fraction 0. + delta_time -= delta_time % (divisor / 1000); + } + os << name_ << ": " << std::setw(8) << FormatDuration(delta_time, tu) << " " + << labels_[i] << "\n"; + } + os << name_ << ": end, " << NsToMs(GetTotalNs()) << " ms\n"; +} + +CumulativeLogger::CumulativeLogger(const std::string& name) + : name_(name), + lock_name_("CumulativeLoggerLock" + name), + lock_(lock_name_.c_str(), kDefaultMutexLevel, true) { + Reset(); +} + +CumulativeLogger::~CumulativeLogger() { + STLDeleteElements(&histograms_); +} + +void CumulativeLogger::SetName(const std::string& name) { + name_ = name; +} + +void CumulativeLogger::Start() { + MutexLock mu(Thread::Current(), lock_); + index_ = 0; +} + +void CumulativeLogger::End() { + MutexLock mu(Thread::Current(), lock_); + iterations_++; +} +void CumulativeLogger::Reset() { + MutexLock mu(Thread::Current(), lock_); + iterations_ = 0; + STLDeleteElements(&histograms_); +} + +uint64_t CumulativeLogger::GetTotalNs() const { + return GetTotalTime() * kAdjust; +} + +uint64_t CumulativeLogger::GetTotalTime() const { + MutexLock mu(Thread::Current(), lock_); + uint64_t total = 0; + for (size_t i = 0; i < histograms_.size(); ++i) { + total += histograms_[i]->Sum(); + } + return total; +} + +void CumulativeLogger::AddLogger(const TimingLogger &logger) { + MutexLock mu(Thread::Current(), lock_); + DCHECK_EQ(logger.times_.size(), logger.labels_.size()); + for (size_t i = 1; i < logger.times_.size(); ++i) { + const uint64_t delta_time = logger.times_[i] - logger.times_[i - 1]; + const std::string &label = logger.labels_[i]; + AddPair(label, delta_time); + } +} + +void CumulativeLogger::Dump(std::ostream &os) { + MutexLock mu(Thread::Current(), lock_); + DumpHistogram(os); +} + +void CumulativeLogger::AddPair(const std::string &label, uint64_t delta_time) { + + // Convert delta time to microseconds so that we don't overflow our counters. + delta_time /= kAdjust; + if (index_ >= histograms_.size()) { + Histogram *tmp_hist = new Histogram(label); + tmp_hist->AddValue(delta_time); + histograms_.push_back(tmp_hist); + } else { + histograms_[index_]->AddValue(delta_time); + DCHECK_EQ(label, histograms_[index_]->Name()); + } + index_++; +} + +void CumulativeLogger::DumpHistogram(std::ostream &os) { + os << "Start Dumping histograms for " << iterations_ << " iterations" + << " for " << name_ << "\n"; + for (size_t Idx = 0; Idx < histograms_.size(); Idx++) { + Histogram &hist = *(histograms_[Idx]); + hist.CreateHistogram(); + hist.PrintConfidenceIntervals(os, 0.99); + } + os << "Done Dumping histograms \n"; +} + +} // namespace art diff --git a/src/base/timing_logger.h b/src/base/timing_logger.h new file mode 100644 index 0000000000000000000000000000000000000000..bbcc286ba4ebb525579e5e3f5a84194e1aa73cd8 --- /dev/null +++ b/src/base/timing_logger.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2011 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_SRC_TIMING_LOGGER_H_ +#define ART_SRC_TIMING_LOGGER_H_ + +#include "base/histogram.h" +#include "base/macros.h" +#include "base/mutex.h" + +#include +#include + +namespace art { + +class CumulativeLogger; + +class TimingLogger { + public: + explicit TimingLogger(const std::string& name, bool precise); + void AddSplit(const std::string& label); + void Dump(std::ostream& os) const; + void Reset(); + uint64_t GetTotalNs() const; + + protected: + const std::string name_; + const bool precise_; + std::vector times_; + std::vector labels_; + + friend class CumulativeLogger; +}; + +class CumulativeLogger { + + public: + + explicit CumulativeLogger(const std::string& name); + void prepare_stats(); + ~CumulativeLogger(); + void Start(); + void End(); + void Reset(); + void Dump(std::ostream& os) LOCKS_EXCLUDED(lock_); + uint64_t GetTotalNs() const; + // Allow the name to be modified, particularly when the cumulative logger is a field within a + // parent class that is unable to determine the "name" of a sub-class. + void SetName(const std::string& name); + void AddLogger(const TimingLogger& logger) LOCKS_EXCLUDED(lock_); + + private: + + void AddPair(const std::string &label, uint64_t delta_time) + EXCLUSIVE_LOCKS_REQUIRED(lock_); + void DumpHistogram(std::ostream &os) EXCLUSIVE_LOCKS_REQUIRED(lock_); + uint64_t GetTotalTime() const; + static const uint64_t kAdjust = 1000; + std::vector *> histograms_ GUARDED_BY(lock_); + std::string name_; + const std::string lock_name_; + mutable Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; + size_t index_ GUARDED_BY(lock_); + size_t iterations_ GUARDED_BY(lock_); + + DISALLOW_COPY_AND_ASSIGN(CumulativeLogger); +}; + +} // namespace art + +#endif // ART_SRC_TIMING_LOGGER_H_ diff --git a/src/base/unix_file/README b/src/base/unix_file/README new file mode 100644 index 0000000000000000000000000000000000000000..e9aec22954a5262dc8c63dc436ee2dc7e89985f8 --- /dev/null +++ b/src/base/unix_file/README @@ -0,0 +1,15 @@ +A simple C++ wrapper for Unix file I/O. + +This is intended to be lightweight and easy to use, similar to Java's +RandomAccessFile and related classes. The usual C++ idioms of RAII and "you +don't pay for what you don't use" apply. + +In particular, the basic RandomAccessFile interface is kept small and simple so +it's trivial to add new implementations. + +This code will not log, because it can't know whether that's appropriate in +your application. + +This code will, in general, return -errno on failure. If an operation consisted +of multiple sub-operations, it will return the errno corresponding to the most +relevant operation. diff --git a/src/base/unix_file/fd_file.cc b/src/base/unix_file/fd_file.cc new file mode 100644 index 0000000000000000000000000000000000000000..36f8ba7fc65d6df2ca4363acadc65c4f1a6486fd --- /dev/null +++ b/src/base/unix_file/fd_file.cc @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/logging.h" +#include "base/unix_file/fd_file.h" +#include +#include +#include +#include + +namespace unix_file { + +FdFile::FdFile() : fd_(-1), auto_close_(true) { +} + +FdFile::FdFile(int fd) : fd_(fd), auto_close_(true) { +} + +FdFile::FdFile(int fd, const std::string& path) : fd_(fd), file_path_(path), auto_close_(true) { + CHECK_NE(0U, path.size()); +} + +FdFile::~FdFile() { + if (auto_close_ && fd_ != -1) { + Close(); + } +} + +void FdFile::DisableAutoClose() { + auto_close_ = false; +} + +bool FdFile::Open(const std::string& path, int flags) { + return Open(path, flags, 0640); +} + +bool FdFile::Open(const std::string& path, int flags, mode_t mode) { + CHECK_EQ(fd_, -1) << path; + fd_ = TEMP_FAILURE_RETRY(open(path.c_str(), flags, mode)); + if (fd_ == -1) { + return false; + } + file_path_ = path; + return true; +} + +int FdFile::Close() { + int result = TEMP_FAILURE_RETRY(close(fd_)); + if (result == -1) { + return -errno; + } else { + fd_ = -1; + file_path_ = ""; + return 0; + } +} + +int FdFile::Flush() { + int rc = TEMP_FAILURE_RETRY(fdatasync(fd_)); + return (rc == -1) ? -errno : rc; +} + +int64_t FdFile::Read(char* buf, int64_t byte_count, int64_t offset) const { + int rc = TEMP_FAILURE_RETRY(pread64(fd_, buf, byte_count, offset)); + return (rc == -1) ? -errno : rc; +} + +int FdFile::SetLength(int64_t new_length) { + int rc = TEMP_FAILURE_RETRY(ftruncate64(fd_, new_length)); + return (rc == -1) ? -errno : rc; +} + +int64_t FdFile::GetLength() const { + struct stat s; + int rc = TEMP_FAILURE_RETRY(fstat(fd_, &s)); + return (rc == -1) ? -errno : s.st_size; +} + +int64_t FdFile::Write(const char* buf, int64_t byte_count, int64_t offset) { + int rc = TEMP_FAILURE_RETRY(pwrite64(fd_, buf, byte_count, offset)); + return (rc == -1) ? -errno : rc; +} + +int FdFile::Fd() const { + return fd_; +} + +bool FdFile::IsOpened() const { + return fd_ >= 0; +} + +std::string FdFile::GetPath() const { + return file_path_; +} + +bool FdFile::ReadFully(void* buffer, int64_t byte_count) { + char* ptr = static_cast(buffer); + while (byte_count > 0) { + int bytes_read = TEMP_FAILURE_RETRY(read(fd_, ptr, byte_count)); + if (bytes_read <= 0) { + return false; + } + byte_count -= bytes_read; // Reduce the number of remaining bytes. + ptr += bytes_read; // Move the buffer forward. + } + return true; +} + +bool FdFile::WriteFully(const void* buffer, int64_t byte_count) { + const char* ptr = static_cast(buffer); + while (byte_count > 0) { + int bytes_read = TEMP_FAILURE_RETRY(write(fd_, ptr, byte_count)); + if (bytes_read < 0) { + return false; + } + byte_count -= bytes_read; // Reduce the number of remaining bytes. + ptr += bytes_read; // Move the buffer forward. + } + return true; +} + +} // namespace unix_file diff --git a/src/base/unix_file/fd_file.h b/src/base/unix_file/fd_file.h new file mode 100644 index 0000000000000000000000000000000000000000..2b339613ba79d6c0580752f55fcf7b2a96df2178 --- /dev/null +++ b/src/base/unix_file/fd_file.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2009 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 BASE_UNIX_FILE_FD_FILE_H_ +#define BASE_UNIX_FILE_FD_FILE_H_ + +#include +#include +#include "base/unix_file/random_access_file.h" +#include "base/macros.h" + +namespace unix_file { + +// A RandomAccessFile implementation backed by a file descriptor. +// +// Not thread safe. +class FdFile : public RandomAccessFile { + public: + FdFile(); + // Creates an FdFile using the given file descriptor. Takes ownership of the + // file descriptor. (Use DisableAutoClose to retain ownership.) + explicit FdFile(int fd); + explicit FdFile(int fd, const std::string& path); + + // Destroys an FdFile, closing the file descriptor if Close hasn't already + // been called. (If you care about the return value of Close, call it + // yourself; this is meant to handle failure cases and read-only accesses. + // Note though that calling Close and checking its return value is still no + // guarantee that data actually made it to stable storage.) + virtual ~FdFile(); + + // Opens file 'file_path' using 'flags' and 'mode'. + bool Open(const std::string& file_path, int flags); + bool Open(const std::string& file_path, int flags, mode_t mode); + + // RandomAccessFile API. + virtual int Close(); + virtual int64_t Read(char* buf, int64_t byte_count, int64_t offset) const; + virtual int SetLength(int64_t new_length); + virtual int64_t GetLength() const; + virtual int64_t Write(const char* buf, int64_t byte_count, int64_t offset); + virtual int Flush(); + + // Bonus API. + int Fd() const; + bool IsOpened() const; + std::string GetPath() const; + void DisableAutoClose(); + bool ReadFully(void* buffer, int64_t byte_count); + bool WriteFully(const void* buffer, int64_t byte_count); + + private: + int fd_; + std::string file_path_; + bool auto_close_; + + DISALLOW_COPY_AND_ASSIGN(FdFile); +}; + +} // namespace unix_file + +#endif // BASE_UNIX_FILE_FD_FILE_H_ diff --git a/src/base/unix_file/fd_file_test.cc b/src/base/unix_file/fd_file_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..d620666747556d585d4289c7284a9c524f9875d2 --- /dev/null +++ b/src/base/unix_file/fd_file_test.cc @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/unix_file/fd_file.h" +#include "base/unix_file/random_access_file_test.h" +#include "gtest/gtest.h" + +namespace unix_file { + +class FdFileTest : public RandomAccessFileTest { + protected: + virtual RandomAccessFile* MakeTestFile() { + return new FdFile(fileno(tmpfile())); + } +}; + +TEST_F(FdFileTest, Read) { + TestRead(); +} + +TEST_F(FdFileTest, SetLength) { + TestSetLength(); +} + +TEST_F(FdFileTest, Write) { + TestWrite(); +} + +TEST_F(FdFileTest, UnopenedFile) { + FdFile file; + EXPECT_EQ(-1, file.Fd()); + EXPECT_FALSE(file.IsOpened()); + EXPECT_TRUE(file.GetPath().empty()); +} + +TEST_F(FdFileTest, OpenClose) { + std::string good_path(GetTmpPath("some-file.txt")); + FdFile file; + ASSERT_TRUE(file.Open(good_path, O_CREAT | O_WRONLY)); + EXPECT_GE(file.Fd(), 0); + EXPECT_TRUE(file.IsOpened()); + EXPECT_EQ(0, file.Close()); + EXPECT_EQ(-1, file.Fd()); + EXPECT_FALSE(file.IsOpened()); + EXPECT_TRUE(file.Open(good_path, O_RDONLY)); + EXPECT_GE(file.Fd(), 0); + EXPECT_TRUE(file.IsOpened()); +} + +} // namespace unix_file diff --git a/src/base/unix_file/mapped_file.cc b/src/base/unix_file/mapped_file.cc new file mode 100644 index 0000000000000000000000000000000000000000..b63fdd3bef8a7d96d5fb06d845603525d5b2f04a --- /dev/null +++ b/src/base/unix_file/mapped_file.cc @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/logging.h" +#include "base/unix_file/mapped_file.h" +#include +#include +#include +#include +#include +#include +#include + +namespace unix_file { + +MappedFile::~MappedFile() { +} + +int MappedFile::Close() { + if (IsMapped()) { + Unmap(); + } + return FdFile::Close(); +} + +bool MappedFile::MapReadOnly() { + CHECK(IsOpened()); + CHECK(!IsMapped()); + struct stat st; + int result = TEMP_FAILURE_RETRY(fstat(Fd(), &st)); + if (result == -1) { + PLOG(WARNING) << "Failed to stat file '" << GetPath() << "'"; + return false; + } + file_size_ = st.st_size; + do { + mapped_file_ = mmap(NULL, file_size_, PROT_READ, MAP_PRIVATE, Fd(), 0); + } while (mapped_file_ == MAP_FAILED && errno == EINTR); + if (mapped_file_ == MAP_FAILED) { + PLOG(WARNING) << "Failed to mmap file '" << GetPath() << "' of size " + << file_size_ << " bytes to memory"; + return false; + } + map_mode_ = kMapReadOnly; + return true; +} + +bool MappedFile::MapReadWrite(int64_t file_size) { + CHECK(IsOpened()); + CHECK(!IsMapped()); + int result = TEMP_FAILURE_RETRY(ftruncate64(Fd(), file_size)); + if (result == -1) { + PLOG(ERROR) << "Failed to truncate file '" << GetPath() + << "' to size " << file_size; + return false; + } + file_size_ = file_size; + do { + mapped_file_ = + mmap(NULL, file_size_, PROT_READ | PROT_WRITE, MAP_SHARED, Fd(), 0); + } while (mapped_file_ == MAP_FAILED && errno == EINTR); + if (mapped_file_ == MAP_FAILED) { + PLOG(WARNING) << "Failed to mmap file '" << GetPath() << "' of size " + << file_size_ << " bytes to memory"; + return false; + } + map_mode_ = kMapReadWrite; + return true; +} + +bool MappedFile::Unmap() { + CHECK(IsMapped()); + int result = TEMP_FAILURE_RETRY(munmap(mapped_file_, file_size_)); + if (result == -1) { + PLOG(WARNING) << "Failed unmap file '" << GetPath() << "' of size " + << file_size_; + return false; + } else { + mapped_file_ = NULL; + file_size_ = -1; + return true; + } +} + +int64_t MappedFile::Read(char* buf, int64_t byte_count, int64_t offset) const { + if (IsMapped()) { + if (offset < 0) { + errno = EINVAL; + return -errno; + } + int64_t read_size = std::max(0LL, std::min(byte_count, file_size_ - offset)); + if (read_size > 0) { + memcpy(buf, data() + offset, read_size); + } + return read_size; + } else { + return FdFile::Read(buf, byte_count, offset); + } +} + +int MappedFile::SetLength(int64_t new_length) { + CHECK(!IsMapped()); + return FdFile::SetLength(new_length); +} + +int64_t MappedFile::GetLength() const { + if (IsMapped()) { + return file_size_; + } else { + return FdFile::GetLength(); + } +} + +int MappedFile::Flush() { + int rc = IsMapped() ? TEMP_FAILURE_RETRY(msync(mapped_file_, file_size_, 0)) : FdFile::Flush(); + return rc == -1 ? -errno : 0; +} + +int64_t MappedFile::Write(const char* buf, int64_t byte_count, int64_t offset) { + if (IsMapped()) { + CHECK_EQ(kMapReadWrite, map_mode_); + if (offset < 0) { + errno = EINVAL; + return -errno; + } + int64_t write_size = std::max(0LL, std::min(byte_count, file_size_ - offset)); + if (write_size > 0) { + memcpy(data() + offset, buf, write_size); + } + return write_size; + } else { + return FdFile::Write(buf, byte_count, offset); + } +} + +int64_t MappedFile::size() const { + return GetLength(); +} + +bool MappedFile::IsMapped() const { + return mapped_file_ != NULL && mapped_file_ != MAP_FAILED; +} + +char* MappedFile::data() const { + CHECK(IsMapped()); + return static_cast(mapped_file_); +} + +} // namespace unix_file diff --git a/src/base/unix_file/mapped_file.h b/src/base/unix_file/mapped_file.h new file mode 100644 index 0000000000000000000000000000000000000000..161100b0d584fccfcadf68384ce54c30caf70f52 --- /dev/null +++ b/src/base/unix_file/mapped_file.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BASE_UNIX_FILE_MAPPED_FILE_H_ +#define BASE_UNIX_FILE_MAPPED_FILE_H_ + +#include +#include +#include "base/unix_file/fd_file.h" + +namespace unix_file { + +// Random access file which handles an mmap(2), munmap(2) pair in C++ +// RAII style. When a file is mmapped, the random access file +// interface accesses the mmapped memory directly; otherwise, the +// standard file I/O is used. Whenever a function fails, it returns +// false and errno is set to the corresponding error code. +class MappedFile : public FdFile { + public: + // File modes used in Open(). + enum FileMode { + kReadOnlyMode = O_RDONLY | O_LARGEFILE, + kReadWriteMode = O_CREAT | O_RDWR | O_LARGEFILE, + }; + + MappedFile() : FdFile(), file_size_(-1), mapped_file_(NULL) { + } + // Creates a MappedFile using the given file descriptor. Takes ownership of + // the file descriptor. + explicit MappedFile(int fd) : FdFile(fd), file_size_(-1), mapped_file_(NULL) { + } + + // Unmaps and closes the file if needed. + virtual ~MappedFile(); + + // Maps an opened file to memory in the read-only mode. + bool MapReadOnly(); + + // Maps an opened file to memory in the read-write mode. Before the + // file is mapped, it is truncated to 'file_size' bytes. + bool MapReadWrite(int64_t file_size); + + // Unmaps a mapped file so that, e.g., SetLength() may be invoked. + bool Unmap(); + + // RandomAccessFile API. + // The functions below require that the file is open, but it doesn't + // have to be mapped. + virtual int Close(); + virtual int64_t Read(char* buf, int64_t byte_count, int64_t offset) const; + // SetLength() requires that the file is not mmapped. + virtual int SetLength(int64_t new_length); + virtual int64_t GetLength() const; + virtual int Flush(); + // Write() requires that, if the file is mmapped, it is mmapped in + // the read-write mode. Writes past the end of file are discarded. + virtual int64_t Write(const char* buf, int64_t byte_count, int64_t offset); + + // A convenience method equivalent to GetLength(). + int64_t size() const; + + // Returns true if the file has been mmapped. + bool IsMapped() const; + + // Returns a pointer to the start of the memory mapping once the + // file is successfully mapped; crashes otherwise. + char* data() const; + + private: + enum MapMode { + kMapReadOnly = 1, + kMapReadWrite = 2, + }; + + mutable int64_t file_size_; // May be updated in GetLength(). + void* mapped_file_; + MapMode map_mode_; + + DISALLOW_COPY_AND_ASSIGN(MappedFile); +}; + +} // namespace unix_file + +#endif // BASE_UNIX_FILE_MAPPED_FILE_H_ diff --git a/src/base/unix_file/mapped_file_test.cc b/src/base/unix_file/mapped_file_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..a3b097dcaee24628ed205e61953f1c5e5b9486d0 --- /dev/null +++ b/src/base/unix_file/mapped_file_test.cc @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/unix_file/mapped_file.h" +#include "base/logging.h" +#include "base/unix_file/fd_file.h" +#include "base/unix_file/random_access_file_test.h" +#include "base/unix_file/random_access_file_utils.h" +#include "base/unix_file/string_file.h" +#include "gtest/gtest.h" + +namespace unix_file { + +class MappedFileTest : public RandomAccessFileTest { + protected: + MappedFileTest() : kContent("some content") { + } + + void SetUp() { + art::CommonTest::SetEnvironmentVariables(android_data_); + + good_path_ = GetTmpPath("some-file.txt"); + int fd = TEMP_FAILURE_RETRY(open(good_path_.c_str(), O_CREAT|O_RDWR, 0666)); + FdFile dst(fd); + + StringFile src; + src.Assign(kContent); + + ASSERT_TRUE(CopyFile(src, &dst)); + } + + virtual RandomAccessFile* MakeTestFile() { + TEMP_FAILURE_RETRY(truncate(good_path_.c_str(), 0)); + MappedFile* f = new MappedFile; + CHECK(f->Open(good_path_, MappedFile::kReadWriteMode)); + return f; + } + + const std::string kContent; + std::string good_path_; +}; + +TEST_F(MappedFileTest, OkayToNotUse) { + MappedFile file; + EXPECT_EQ(-1, file.Fd()); + EXPECT_FALSE(file.IsOpened()); + EXPECT_FALSE(file.IsMapped()); +} + +TEST_F(MappedFileTest, OpenClose) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + EXPECT_GE(file.Fd(), 0); + EXPECT_TRUE(file.IsOpened()); + EXPECT_EQ(kContent.size(), file.size()); + EXPECT_EQ(0, file.Close()); + EXPECT_EQ(-1, file.Fd()); + EXPECT_FALSE(file.IsOpened()); +} + +TEST_F(MappedFileTest, OpenFdClose) { + FILE* f = tmpfile(); + ASSERT_TRUE(f != NULL); + MappedFile file(fileno(f)); + EXPECT_GE(file.Fd(), 0); + EXPECT_TRUE(file.IsOpened()); + EXPECT_EQ(0, file.Close()); +} + +TEST_F(MappedFileTest, CanUseAfterMapReadOnly) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + EXPECT_FALSE(file.IsMapped()); + EXPECT_TRUE(file.MapReadOnly()); + EXPECT_TRUE(file.IsMapped()); + EXPECT_EQ(kContent.size(), file.size()); + ASSERT_TRUE(file.data()); + EXPECT_EQ(0, memcmp(kContent.c_str(), file.data(), file.size())); + EXPECT_EQ(0, file.Flush()); +} + +TEST_F(MappedFileTest, CanUseAfterMapReadWrite) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode)); + EXPECT_FALSE(file.IsMapped()); + EXPECT_TRUE(file.MapReadWrite(1)); + EXPECT_TRUE(file.IsMapped()); + EXPECT_EQ(1, file.size()); + ASSERT_TRUE(file.data()); + EXPECT_EQ(kContent[0], *file.data()); + EXPECT_EQ(0, file.Flush()); +} + +TEST_F(MappedFileTest, CanWriteNewData) { + const std::string new_path(GetTmpPath("new-file.txt")); + ASSERT_EQ(-1, unlink(new_path.c_str())); + ASSERT_EQ(ENOENT, errno); + + MappedFile file; + ASSERT_TRUE(file.Open(new_path, MappedFile::kReadWriteMode)); + EXPECT_TRUE(file.MapReadWrite(kContent.size())); + EXPECT_TRUE(file.IsMapped()); + EXPECT_EQ(kContent.size(), file.size()); + ASSERT_TRUE(file.data()); + memcpy(file.data(), kContent.c_str(), kContent.size()); + EXPECT_EQ(0, file.Close()); + EXPECT_FALSE(file.IsMapped()); + + FdFile new_file(TEMP_FAILURE_RETRY(open(new_path.c_str(), O_RDONLY))); + StringFile buffer; + ASSERT_TRUE(CopyFile(new_file, &buffer)); + EXPECT_EQ(kContent, buffer.ToStringPiece()); + EXPECT_EQ(0, unlink(new_path.c_str())); +} + +TEST_F(MappedFileTest, FileMustExist) { + const std::string bad_path(GetTmpPath("does-not-exist.txt")); + MappedFile file; + EXPECT_FALSE(file.Open(bad_path, MappedFile::kReadOnlyMode)); + EXPECT_EQ(-1, file.Fd()); +} + +TEST_F(MappedFileTest, FileMustBeWritable) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + EXPECT_FALSE(file.MapReadWrite(10)); +} + +TEST_F(MappedFileTest, RemappingAllowedUntilSuccess) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + EXPECT_FALSE(file.MapReadWrite(10)); + EXPECT_FALSE(file.MapReadWrite(10)); +} + +TEST_F(MappedFileTest, ResizeMappedFile) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode)); + ASSERT_TRUE(file.MapReadWrite(10)); + EXPECT_EQ(10, file.GetLength()); + EXPECT_TRUE(file.Unmap()); + EXPECT_TRUE(file.MapReadWrite(20)); + EXPECT_EQ(20, file.GetLength()); + EXPECT_EQ(0, file.Flush()); + EXPECT_TRUE(file.Unmap()); + EXPECT_EQ(0, file.Flush()); + EXPECT_EQ(0, file.SetLength(5)); + EXPECT_TRUE(file.MapReadOnly()); + EXPECT_EQ(5, file.GetLength()); +} + +TEST_F(MappedFileTest, ReadNotMapped) { + TestRead(); +} + +TEST_F(MappedFileTest, SetLengthNotMapped) { + TestSetLength(); +} + +TEST_F(MappedFileTest, WriteNotMapped) { + TestWrite(); +} + +TEST_F(MappedFileTest, ReadMappedReadOnly) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + ASSERT_TRUE(file.MapReadOnly()); + TestReadContent(kContent, &file); +} + +TEST_F(MappedFileTest, ReadMappedReadWrite) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode)); + ASSERT_TRUE(file.MapReadWrite(kContent.size())); + TestReadContent(kContent, &file); +} + +TEST_F(MappedFileTest, WriteMappedReadWrite) { + TEMP_FAILURE_RETRY(unlink(good_path_.c_str())); + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode)); + ASSERT_TRUE(file.MapReadWrite(kContent.size())); + + // Can't write to a negative offset. + EXPECT_EQ(-EINVAL, file.Write(kContent.c_str(), 0, -123)); + + // A zero-length write is a no-op. + EXPECT_EQ(0, file.Write(kContent.c_str(), 0, 0)); + // But the file size is as given when mapped. + EXPECT_EQ(kContent.size(), file.GetLength()); + + // Data written past the end are discarded. + EXPECT_EQ(kContent.size() - 1, + file.Write(kContent.c_str(), kContent.size(), 1)); + EXPECT_EQ(0, memcmp(kContent.c_str(), file.data() + 1, kContent.size() - 1)); + + // Data can be overwritten. + EXPECT_EQ(kContent.size(), file.Write(kContent.c_str(), kContent.size(), 0)); + EXPECT_EQ(0, memcmp(kContent.c_str(), file.data(), kContent.size())); +} + +#if 0 // death tests don't work on android yet + +class MappedFileDeathTest : public MappedFileTest {}; + +TEST_F(MappedFileDeathTest, MustMapBeforeUse) { + MappedFile file; + EXPECT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + EXPECT_DEATH(file.data(), "mapped_"); +} + +TEST_F(MappedFileDeathTest, RemappingNotAllowedReadOnly) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + ASSERT_TRUE(file.MapReadOnly()); + EXPECT_DEATH(file.MapReadOnly(), "mapped_"); +} + +TEST_F(MappedFileDeathTest, RemappingNotAllowedReadWrite) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode)); + ASSERT_TRUE(file.MapReadWrite(10)); + EXPECT_DEATH(file.MapReadWrite(10), "mapped_"); +} + +TEST_F(MappedFileDeathTest, SetLengthMappedReadWrite) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode)); + ASSERT_TRUE(file.MapReadWrite(10)); + EXPECT_EQ(10, file.GetLength()); + EXPECT_DEATH(file.SetLength(0), ".*"); +} + +TEST_F(MappedFileDeathTest, SetLengthMappedReadOnly) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + ASSERT_TRUE(file.MapReadOnly()); + EXPECT_EQ(kContent.size(), file.GetLength()); + EXPECT_DEATH(file.SetLength(0), ".*"); +} + +TEST_F(MappedFileDeathTest, WriteMappedReadOnly) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + ASSERT_TRUE(file.MapReadOnly()); + char buf[10]; + EXPECT_DEATH(file.Write(buf, 0, 0), ".*"); +} + +#endif + +} // namespace unix_file diff --git a/src/base/unix_file/null_file.cc b/src/base/unix_file/null_file.cc new file mode 100644 index 0000000000000000000000000000000000000000..050decb6dbddb8cb1ad5a25fabdf34605610e5ea --- /dev/null +++ b/src/base/unix_file/null_file.cc @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/unix_file/null_file.h" +#include + +namespace unix_file { + +NullFile::NullFile() { +} + +NullFile::~NullFile() { +} + +int NullFile::Close() { + return 0; +} + +int NullFile::Flush() { + return 0; +} + +int64_t NullFile::Read(char* buf, int64_t byte_count, int64_t offset) const { + if (offset < 0) { + return -EINVAL; + } + return 0; +} + +int NullFile::SetLength(int64_t new_length) { + if (new_length < 0) { + return -EINVAL; + } + return 0; +} + +int64_t NullFile::GetLength() const { + return 0; +} + +int64_t NullFile::Write(const char* buf, int64_t byte_count, int64_t offset) { + if (offset < 0) { + return -EINVAL; + } + return byte_count; +} + +} // namespace unix_file diff --git a/src/base/unix_file/null_file.h b/src/base/unix_file/null_file.h new file mode 100644 index 0000000000000000000000000000000000000000..e7166036871613384a2f6fafae53a26111f53c34 --- /dev/null +++ b/src/base/unix_file/null_file.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2009 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 BASE_UNIX_FILE_NULL_FILE_H_ +#define BASE_UNIX_FILE_NULL_FILE_H_ + +#include "base/unix_file/random_access_file.h" +#include "base/macros.h" + +namespace unix_file { + +// A RandomAccessFile implementation equivalent to /dev/null. Writes are +// discarded, and there's no data to be read. Callers could use FdFile in +// conjunction with /dev/null, but that's not portable and costs a file +// descriptor. NullFile is "free". +// +// Thread safe. +class NullFile : public RandomAccessFile { + public: + NullFile(); + virtual ~NullFile(); + + // RandomAccessFile API. + virtual int Close(); + virtual int Flush(); + virtual int64_t Read(char* buf, int64_t byte_count, int64_t offset) const; + virtual int SetLength(int64_t new_length); + virtual int64_t GetLength() const; + virtual int64_t Write(const char* buf, int64_t byte_count, int64_t offset); + + private: + DISALLOW_COPY_AND_ASSIGN(NullFile); +}; + +} // namespace unix_file + +#endif // BASE_UNIX_FILE_NULL_FILE_H_ diff --git a/src/base/unix_file/null_file_test.cc b/src/base/unix_file/null_file_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..0f20acd825a2892f3032cba2a4ce300fe7f5a990 --- /dev/null +++ b/src/base/unix_file/null_file_test.cc @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/unix_file/null_file.h" + +#include + +#include "gtest/gtest.h" + +namespace unix_file { + +class NullFileTest : public testing::Test { }; + +TEST_F(NullFileTest, Read) { + NullFile f; + char buf[256]; + // You can't read a negative number of bytes... + ASSERT_EQ(-EINVAL, f.Read(buf, 0, -1)); + // ...but everything else is fine (though you'll get no data). + ASSERT_EQ(0, f.Read(buf, 128, 0)); + ASSERT_EQ(0, f.Read(buf, 128, 128)); +} + +TEST_F(NullFileTest, SetLength) { + NullFile f; + // You can't set a negative length... + ASSERT_EQ(-EINVAL, f.SetLength(-1)); + // ...but everything else is fine. + ASSERT_EQ(0, f.SetLength(0)); + ASSERT_EQ(0, f.SetLength(128)); +} + +TEST_F(NullFileTest, GetLength) { + const std::string content("hello"); + NullFile f; + // The length is always 0. + ASSERT_EQ(0, f.GetLength()); + ASSERT_EQ(content.size(), f.Write(content.data(), content.size(), 0)); + ASSERT_EQ(0, f.GetLength()); +} + +TEST_F(NullFileTest, Write) { + const std::string content("hello"); + NullFile f; + // You can't write at a negative offset... + ASSERT_EQ(-EINVAL, f.Write(content.data(), content.size(), -128)); + // But you can write anywhere else... + ASSERT_EQ(content.size(), f.Write(content.data(), content.size(), 0)); + ASSERT_EQ(content.size(), f.Write(content.data(), content.size(), 128)); + // ...though the file will remain empty. + ASSERT_EQ(0, f.GetLength()); +} + +} // namespace unix_file diff --git a/src/base/unix_file/random_access_file.h b/src/base/unix_file/random_access_file.h new file mode 100644 index 0000000000000000000000000000000000000000..22da37f03e75569c08343d1fabd5eb76bac8bd45 --- /dev/null +++ b/src/base/unix_file/random_access_file.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009 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 BASE_UNIX_FILE_RANDOM_ACCESS_FILE_H_ +#define BASE_UNIX_FILE_RANDOM_ACCESS_FILE_H_ + +#include + +namespace unix_file { + +// A file interface supporting random-access reading and writing of content, +// along with the ability to set the length of a file (smaller or greater than +// its current extent). +// +// This interface does not support a stream position (i.e. every read or write +// must specify an offset). This interface does not imply any buffering policy. +// +// All operations return >= 0 on success or -errno on failure. +// +// Implementations never return EINTR; callers are spared the need to manually +// retry interrupted operations. +// +// Any concurrent access to files should be externally synchronized. +class RandomAccessFile { + public: + virtual ~RandomAccessFile() { } + + virtual int Close() = 0; + + // Reads 'byte_count' bytes into 'buf' starting at offset 'offset' in the + // file. Returns the number of bytes actually read. + virtual int64_t Read(char* buf, int64_t byte_count, int64_t offset) const = 0; + + // Sets the length of the file to 'new_length'. If this is smaller than the + // file's current extent, data is discarded. If this is greater than the + // file's current extent, it is as if a write of the relevant number of zero + // bytes occurred. Returns 0 on success. + virtual int SetLength(int64_t new_length) = 0; + + // Returns the current size of this file. + virtual int64_t GetLength() const = 0; + + // Writes 'byte_count' bytes from 'buf' starting at offset 'offset' in the + // file. Zero-byte writes are acceptable, and writes past the end are as if + // a write of the relevant number of zero bytes also occurred. Returns the + // number of bytes actually written. + virtual int64_t Write(const char* buf, int64_t byte_count, int64_t offset) = 0; + + // Flushes file data. + virtual int Flush() = 0; +}; + +} // namespace unix_file + +#endif // BASE_UNIX_FILE_RANDOM_ACCESS_FILE_H_ diff --git a/src/base/unix_file/random_access_file_test.h b/src/base/unix_file/random_access_file_test.h new file mode 100644 index 0000000000000000000000000000000000000000..3baaeae8ac766c6c3b16957fd04d80b34dfcea52 --- /dev/null +++ b/src/base/unix_file/random_access_file_test.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2009 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 BASE_UNIX_FILE_RANDOM_ACCESS_FILE_TEST_H_ +#define BASE_UNIX_FILE_RANDOM_ACCESS_FILE_TEST_H_ + +#include + +#include + +#include "common_test.h" +#include "gtest/gtest.h" +#include "UniquePtr.h" + +namespace unix_file { + +class RandomAccessFileTest : public testing::Test { + protected: + virtual ~RandomAccessFileTest() { + } + + // Override this to return an instance of the subclass under test that's + // backed by a temporary file. + virtual RandomAccessFile* MakeTestFile() = 0; + + virtual void SetUp() { + art::CommonTest::SetEnvironmentVariables(android_data_); + } + + std::string GetTmpPath(const std::string& name) { + std::string path; + path = android_data_; + path += "/"; + path += name; + return path; + } + + // TODO(enh): ReadString (and WriteString) might be generally useful. + static bool ReadString(RandomAccessFile* f, std::string* s) { + s->clear(); + char buf[256]; + int64_t n = 0; + int64_t offset = 0; + while ((n = f->Read(buf, sizeof(buf), offset)) > 0) { + s->append(buf, n); + offset += n; + } + return n != -1; + } + + void TestRead() { + char buf[256]; + UniquePtr file(MakeTestFile()); + + // Reading from the start of an empty file gets you zero bytes, however many + // you ask for. + ASSERT_EQ(0, file->Read(buf, 0, 0)); + ASSERT_EQ(0, file->Read(buf, 123, 0)); + + const std::string content("hello"); + ASSERT_EQ(content.size(), file->Write(content.data(), content.size(), 0)); + + TestReadContent(content, file.get()); + } + + void TestReadContent(const std::string& content, RandomAccessFile* file) { + const int buf_size = content.size() + 10; + UniquePtr buf(new char[buf_size]); + // Can't read from a negative offset. + ASSERT_EQ(-EINVAL, file->Read(buf.get(), 0, -123)); + + // Reading too much gets us just what's in the file. + ASSERT_EQ(content.size(), file->Read(buf.get(), buf_size, 0)); + ASSERT_EQ(std::string(buf.get(), content.size()), content); + + // We only get as much as we ask for. + const size_t short_request = 2; + ASSERT_LT(short_request, content.size()); + ASSERT_EQ(short_request, file->Read(buf.get(), short_request, 0)); + ASSERT_EQ(std::string(buf.get(), short_request), + content.substr(0, short_request)); + + // We don't have to start at the beginning. + const int non_zero_offset = 2; + ASSERT_GT(non_zero_offset, 0); + ASSERT_EQ(short_request, + file->Read(buf.get(), short_request, non_zero_offset)); + ASSERT_EQ(std::string(buf.get(), short_request), + content.substr(non_zero_offset, short_request)); + + // Reading past the end gets us nothing. + ASSERT_EQ(0, file->Read(buf.get(), buf_size, file->GetLength())); + ASSERT_EQ(0, file->Read(buf.get(), buf_size, file->GetLength() + 1)); + } + + void TestSetLength() { + const std::string content("hello"); + UniquePtr file(MakeTestFile()); + ASSERT_EQ(content.size(), file->Write(content.data(), content.size(), 0)); + ASSERT_EQ(content.size(), file->GetLength()); + + // Can't give a file a negative length. + ASSERT_EQ(-EINVAL, file->SetLength(-123)); + + // Can truncate the file. + int64_t new_length = 2; + ASSERT_EQ(0, file->SetLength(new_length)); + ASSERT_EQ(new_length, file->GetLength()); + std::string new_content; + ASSERT_TRUE(ReadString(file.get(), &new_content)); + ASSERT_EQ(content.substr(0, 2), new_content); + + // Expanding the file appends zero bytes. + new_length = file->GetLength() + 1; + ASSERT_EQ(0, file->SetLength(new_length)); + ASSERT_EQ(new_length, file->GetLength()); + ASSERT_TRUE(ReadString(file.get(), &new_content)); + ASSERT_EQ('\0', new_content[new_length - 1]); + } + + void TestWrite() { + const std::string content("hello"); + UniquePtr file(MakeTestFile()); + + // Can't write to a negative offset. + ASSERT_EQ(-EINVAL, file->Write(content.data(), 0, -123)); + + // Writing zero bytes of data is a no-op. + ASSERT_EQ(0, file->Write(content.data(), 0, 0)); + ASSERT_EQ(0, file->GetLength()); + + // We can write data. + ASSERT_EQ(content.size(), file->Write(content.data(), content.size(), 0)); + ASSERT_EQ(content.size(), file->GetLength()); + std::string new_content; + ASSERT_TRUE(ReadString(file.get(), &new_content)); + ASSERT_EQ(new_content, content); + + // We can read it back. + char buf[256]; + ASSERT_EQ(content.size(), file->Read(buf, sizeof(buf), 0)); + ASSERT_EQ(std::string(buf, content.size()), content); + + // We can append data past the end. + ASSERT_EQ(content.size(), + file->Write(content.data(), content.size(), file->GetLength() + 1)); + int64_t new_length = 2*content.size() + 1; + ASSERT_EQ(file->GetLength(), new_length); + ASSERT_TRUE(ReadString(file.get(), &new_content)); + ASSERT_EQ(std::string("hello\0hello", new_length), new_content); + } + + protected: + std::string android_data_; +}; + +} // namespace unix_file + +#endif // BASE_UNIX_FILE_RANDOM_ACCESS_FILE_TEST_H_ diff --git a/src/base/unix_file/random_access_file_utils.cc b/src/base/unix_file/random_access_file_utils.cc new file mode 100644 index 0000000000000000000000000000000000000000..df3b308bb0363015fd6d7f4aa73c05675e8774b7 --- /dev/null +++ b/src/base/unix_file/random_access_file_utils.cc @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "base/unix_file/random_access_file_utils.h" +#include "base/unix_file/random_access_file.h" + +namespace unix_file { + +bool CopyFile(const RandomAccessFile& src, RandomAccessFile* dst) { + // We don't call src->GetLength because some files (those in /proc, say) + // don't know how long they are. We just read until there's nothing left. + std::vector buf(4096); + int64_t offset = 0; + int64_t n; + while ((n = src.Read(&buf[0], buf.size(), offset)) > 0) { + if (dst->Write(&buf[0], n, offset) != n) { + return false; + } + offset += n; + } + return n >= 0; +} + +} // namespace unix_file diff --git a/src/base/unix_file/random_access_file_utils.h b/src/base/unix_file/random_access_file_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..0535ead8c50a6fc547858c5e7c49199f2857255d --- /dev/null +++ b/src/base/unix_file/random_access_file_utils.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009 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 BASE_UNIX_FILE_RANDOM_ACCESS_FILE_UTILS_H_ +#define BASE_UNIX_FILE_RANDOM_ACCESS_FILE_UTILS_H_ + +namespace unix_file { + +class RandomAccessFile; + +// Copies from 'src' to 'dst'. Reads all the data from 'src', and writes it +// to 'dst'. Not thread-safe. Neither file will be closed. +bool CopyFile(const RandomAccessFile& src, RandomAccessFile* dst); + +} // namespace unix_file + +#endif // BASE_UNIX_FILE_RANDOM_ACCESS_FILE_UTILS_H_ diff --git a/src/base/unix_file/random_access_file_utils_test.cc b/src/base/unix_file/random_access_file_utils_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..63179220a25553b6fbec29ea4ff427294ae6032d --- /dev/null +++ b/src/base/unix_file/random_access_file_utils_test.cc @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/unix_file/random_access_file_utils.h" +#include "base/unix_file/fd_file.h" +#include "base/unix_file/string_file.h" +#include "gtest/gtest.h" + +namespace unix_file { + +class RandomAccessFileUtilsTest : public testing::Test { }; + +TEST_F(RandomAccessFileUtilsTest, CopyFile) { + StringFile src; + StringFile dst; + + const std::string content("hello"); + src.Assign(content); + ASSERT_EQ(src.ToStringPiece(), content); + ASSERT_EQ(dst.ToStringPiece(), ""); + + ASSERT_TRUE(CopyFile(src, &dst)); + ASSERT_EQ(src.ToStringPiece(), dst.ToStringPiece()); +} + +TEST_F(RandomAccessFileUtilsTest, BadSrc) { + FdFile src(-1); + StringFile dst; + ASSERT_FALSE(CopyFile(src, &dst)); +} + +TEST_F(RandomAccessFileUtilsTest, BadDst) { + StringFile src; + FdFile dst(-1); + + // We need some source content to trigger a write. + // Copying an empty file is a no-op. + src.Assign("hello"); + + ASSERT_FALSE(CopyFile(src, &dst)); +} + +} // namespace unix_file diff --git a/src/base/unix_file/string_file.cc b/src/base/unix_file/string_file.cc new file mode 100644 index 0000000000000000000000000000000000000000..ff0d0fa3c460fdd9235b8674cc57f4979e905766 --- /dev/null +++ b/src/base/unix_file/string_file.cc @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/unix_file/string_file.h" +#include +#include +#include "base/logging.h" + +namespace unix_file { + +StringFile::StringFile() { +} + +StringFile::~StringFile() { +} + +int StringFile::Close() { + return 0; +} + +int StringFile::Flush() { + return 0; +} + +int64_t StringFile::Read(char *buf, int64_t byte_count, int64_t offset) const { + CHECK(buf); + CHECK_GE(byte_count, 0); + + if (offset < 0) { + return -EINVAL; + } + + const int64_t available_bytes = std::min(byte_count, GetLength() - offset); + if (available_bytes < 0) { + return 0; // Not an error, but nothing for us to do, either. + } + memcpy(buf, data_.data() + offset, available_bytes); + return available_bytes; +} + +int StringFile::SetLength(int64_t new_length) { + if (new_length < 0) { + return -EINVAL; + } + data_.resize(new_length); + return 0; +} + +int64_t StringFile::GetLength() const { + return data_.size(); +} + +int64_t StringFile::Write(const char *buf, int64_t byte_count, int64_t offset) { + CHECK(buf); + CHECK_GE(byte_count, 0); + + if (offset < 0) { + return -EINVAL; + } + + if (byte_count == 0) { + return 0; + } + + // FUSE seems happy to allow writes past the end. (I'd guess it doesn't + // synthesize a write of zero bytes so that we're free to implement sparse + // files.) GNU as(1) seems to require such writes. Those files are small. + const int64_t bytes_past_end = offset - GetLength(); + if (bytes_past_end > 0) { + data_.append(bytes_past_end, '\0'); + } + + data_.replace(offset, byte_count, buf, byte_count); + return byte_count; +} + +void StringFile::Assign(const art::StringPiece &new_data) { + data_.assign(new_data.data(), new_data.size()); +} + +const art::StringPiece StringFile::ToStringPiece() const { + return data_; +} + +} // namespace unix_file diff --git a/src/base/unix_file/string_file.h b/src/base/unix_file/string_file.h new file mode 100644 index 0000000000000000000000000000000000000000..89443733447cdea7e80efdaa23d2a312dafcbf72 --- /dev/null +++ b/src/base/unix_file/string_file.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2009 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 BASE_UNIX_FILE_STRING_FILE_H_ +#define BASE_UNIX_FILE_STRING_FILE_H_ + +#include + +#include + +#include "base/macros.h" +#include "base/stringpiece.h" +#include "base/unix_file/random_access_file.h" + +namespace unix_file { + +// A RandomAccessFile implementation backed by a std::string. (That is, all data is +// kept in memory.) +// +// Not thread safe. +class StringFile : public RandomAccessFile { + public: + StringFile(); + virtual ~StringFile(); + + // RandomAccessFile API. + virtual int Close(); + virtual int Flush(); + virtual int64_t Read(char* buf, int64_t byte_count, int64_t offset) const; + virtual int SetLength(int64_t new_length); + virtual int64_t GetLength() const; + virtual int64_t Write(const char* buf, int64_t byte_count, int64_t offset); + + // Bonus API. + void Assign(const art::StringPiece& new_data); + const art::StringPiece ToStringPiece() const; + + private: + std::string data_; + + DISALLOW_COPY_AND_ASSIGN(StringFile); +}; + +} // namespace unix_file + +#endif // BASE_UNIX_FILE_STRING_FILE_H_ diff --git a/src/base/unix_file/string_file_test.cc b/src/base/unix_file/string_file_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..88214610c4bd1613774909064182835aa1df8719 --- /dev/null +++ b/src/base/unix_file/string_file_test.cc @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/unix_file/string_file.h" +#include "base/unix_file/random_access_file_test.h" +#include "gtest/gtest.h" + +namespace unix_file { + +class StringFileTest : public RandomAccessFileTest { + protected: + virtual RandomAccessFile* MakeTestFile() { + return new StringFile; + } +}; + +TEST_F(StringFileTest, Read) { + TestRead(); +} + +TEST_F(StringFileTest, SetLength) { + TestSetLength(); +} + +TEST_F(StringFileTest, Write) { + TestWrite(); +} + +} // namespace unix_file diff --git a/src/check_jni.cc b/src/check_jni.cc new file mode 100644 index 0000000000000000000000000000000000000000..19f8abfee077d3143cbd3a82d34010de5d28de41 --- /dev/null +++ b/src/check_jni.cc @@ -0,0 +1,2095 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jni_internal.h" + +#include +#include + +#include "base/logging.h" +#include "class_linker.h" +#include "class_linker-inl.h" +#include "dex_file-inl.h" +#include "gc/space.h" +#include "mirror/class-inl.h" +#include "mirror/field-inl.h" +#include "mirror/abstract_method-inl.h" +#include "mirror/object-inl.h" +#include "mirror/object_array-inl.h" +#include "mirror/throwable.h" +#include "object_utils.h" +#include "runtime.h" +#include "scoped_thread_state_change.h" +#include "thread.h" + +#define LIBCORE_CPP_JNI_HELPERS +#include // from libcore +#undef LIBCORE_CPP_JNI_HELPERS + +namespace art { + +static void JniAbort(const char* jni_function_name, const char* msg) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + mirror::AbstractMethod* current_method = self->GetCurrentMethod(NULL); + + std::ostringstream os; + os << "JNI DETECTED ERROR IN APPLICATION: " << msg; + + if (jni_function_name != NULL) { + os << "\n in call to " << jni_function_name; + } + // TODO: is this useful given that we're about to dump the calling thread's stack? + if (current_method != NULL) { + os << "\n from " << PrettyMethod(current_method); + } + os << "\n"; + self->Dump(os); + + JavaVMExt* vm = Runtime::Current()->GetJavaVM(); + if (vm->check_jni_abort_hook != NULL) { + vm->check_jni_abort_hook(vm->check_jni_abort_hook_data, os.str()); + } else { + // Ensure that we get a native stack trace for this thread. + self->TransitionFromRunnableToSuspended(kNative); + LOG(FATAL) << os.str(); + self->TransitionFromSuspendedToRunnable(); // Unreachable, keep annotalysis happy. + } +} + +static void JniAbortV(const char* jni_function_name, const char* fmt, va_list ap) { + std::string msg; + StringAppendV(&msg, fmt, ap); + JniAbort(jni_function_name, msg.c_str()); +} + +void JniAbortF(const char* jni_function_name, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + JniAbortV(jni_function_name, fmt, args); + va_end(args); +} + +/* + * =========================================================================== + * JNI function helpers + * =========================================================================== + */ + +static bool IsSirtLocalRef(JNIEnv* env, jobject localRef) { + return GetIndirectRefKind(localRef) == kSirtOrInvalid && + reinterpret_cast(env)->self->SirtContains(localRef); +} + +// Hack to allow forcecopy to work with jniGetNonMovableArrayElements. +// The code deliberately uses an invalid sequence of operations, so we +// need to pass it through unmodified. Review that code before making +// any changes here. +#define kNoCopyMagic 0xd5aab57f + +// Flags passed into ScopedCheck. +#define kFlag_Default 0x0000 + +#define kFlag_CritBad 0x0000 // Calling while in critical is not allowed. +#define kFlag_CritOkay 0x0001 // Calling while in critical is allowed. +#define kFlag_CritGet 0x0002 // This is a critical "get". +#define kFlag_CritRelease 0x0003 // This is a critical "release". +#define kFlag_CritMask 0x0003 // Bit mask to get "crit" value. + +#define kFlag_ExcepBad 0x0000 // Raised exceptions are not allowed. +#define kFlag_ExcepOkay 0x0004 // Raised exceptions are allowed. + +#define kFlag_Release 0x0010 // Are we in a non-critical release function? +#define kFlag_NullableUtf 0x0020 // Are our UTF parameters nullable? + +#define kFlag_Invocation 0x8000 // Part of the invocation interface (JavaVM*). + +#define kFlag_ForceTrace 0x80000000 // Add this to a JNI function's flags if you want to trace every call. + +static const char* gBuiltInPrefixes[] = { + "Landroid/", + "Lcom/android/", + "Lcom/google/android/", + "Ldalvik/", + "Ljava/", + "Ljavax/", + "Llibcore/", + "Lorg/apache/harmony/", + NULL +}; + +static bool ShouldTrace(JavaVMExt* vm, const mirror::AbstractMethod* method) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + // If both "-Xcheck:jni" and "-Xjnitrace:" are enabled, we print trace messages + // when a native method that matches the -Xjnitrace argument calls a JNI function + // such as NewByteArray. + // If -verbose:third-party-jni is on, we want to log any JNI function calls + // made by a third-party native method. + std::string class_name(MethodHelper(method).GetDeclaringClassDescriptor()); + if (!vm->trace.empty() && class_name.find(vm->trace) != std::string::npos) { + return true; + } + if (VLOG_IS_ON(third_party_jni)) { + // Return true if we're trying to log all third-party JNI activity and 'method' doesn't look + // like part of Android. + for (size_t i = 0; gBuiltInPrefixes[i] != NULL; ++i) { + if (StartsWith(class_name, gBuiltInPrefixes[i])) { + return false; + } + } + return true; + } + return false; +} + +class ScopedCheck { + public: + // For JNIEnv* functions. + explicit ScopedCheck(JNIEnv* env, int flags, const char* functionName) + SHARED_LOCK_FUNCTION(Locks::mutator_lock_) + : soa_(env) { + Init(flags, functionName, true); + CheckThread(flags); + } + + // For JavaVM* functions. + // TODO: it's not correct that this is a lock function, but making it so aids annotalysis. + explicit ScopedCheck(JavaVM* vm, bool has_method, const char* functionName) + SHARED_LOCK_FUNCTION(Locks::mutator_lock_) + : soa_(vm) { + Init(kFlag_Invocation, functionName, has_method); + } + + ~ScopedCheck() UNLOCK_FUNCTION(Locks::mutator_lock_) {} + + const ScopedObjectAccess& soa() { + return soa_; + } + + bool ForceCopy() { + return Runtime::Current()->GetJavaVM()->force_copy; + } + + // Checks that 'class_name' is a valid "fully-qualified" JNI class name, like "java/lang/Thread" + // or "[Ljava/lang/Object;". A ClassLoader can actually normalize class names a couple of + // times, so using "java.lang.Thread" instead of "java/lang/Thread" might work in some + // circumstances, but this is incorrect. + void CheckClassName(const char* class_name) { + if (!IsValidJniClassName(class_name)) { + JniAbortF(function_name_, + "illegal class name '%s'\n" + " (should be of the form 'package/Class', [Lpackage/Class;' or '[[B')", + class_name); + } + } + + /* + * Verify that the field is of the appropriate type. If the field has an + * object type, "java_object" is the object we're trying to assign into it. + * + * Works for both static and instance fields. + */ + void CheckFieldType(jobject java_object, jfieldID fid, char prim, bool isStatic) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::Field* f = CheckFieldID(fid); + if (f == NULL) { + return; + } + mirror::Class* field_type = FieldHelper(f).GetType(); + if (!field_type->IsPrimitive()) { + if (java_object != NULL) { + mirror::Object* obj = soa_.Decode(java_object); + // If java_object is a weak global ref whose referent has been cleared, + // obj will be NULL. Otherwise, obj should always be non-NULL + // and valid. + if (!Runtime::Current()->GetHeap()->IsHeapAddress(obj)) { + Runtime::Current()->GetHeap()->DumpSpaces(); + JniAbortF(function_name_, "field operation on invalid %s: %p", + ToStr(GetIndirectRefKind(java_object)).c_str(), java_object); + return; + } else { + if (!obj->InstanceOf(field_type)) { + JniAbortF(function_name_, "attempt to set field %s with value of wrong type: %s", + PrettyField(f).c_str(), PrettyTypeOf(obj).c_str()); + return; + } + } + } + } else if (field_type != Runtime::Current()->GetClassLinker()->FindPrimitiveClass(prim)) { + JniAbortF(function_name_, "attempt to set field %s with value of wrong type: %c", + PrettyField(f).c_str(), prim); + return; + } + + if (isStatic != f->IsStatic()) { + if (isStatic) { + JniAbortF(function_name_, "accessing non-static field %s as static", PrettyField(f).c_str()); + } else { + JniAbortF(function_name_, "accessing static field %s as non-static", PrettyField(f).c_str()); + } + return; + } + } + + /* + * Verify that this instance field ID is valid for this object. + * + * Assumes "jobj" has already been validated. + */ + void CheckInstanceFieldID(jobject java_object, jfieldID fid) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::Object* o = soa_.Decode(java_object); + if (o == NULL || !Runtime::Current()->GetHeap()->IsHeapAddress(o)) { + Runtime::Current()->GetHeap()->DumpSpaces(); + JniAbortF(function_name_, "field operation on invalid %s: %p", + ToStr(GetIndirectRefKind(java_object)).c_str(), java_object); + return; + } + + mirror::Field* f = CheckFieldID(fid); + if (f == NULL) { + return; + } + mirror::Class* c = o->GetClass(); + FieldHelper fh(f); + if (c->FindInstanceField(fh.GetName(), fh.GetTypeDescriptor()) == NULL) { + JniAbortF(function_name_, "jfieldID %s not valid for an object of class %s", + PrettyField(f).c_str(), PrettyTypeOf(o).c_str()); + } + } + + /* + * Verify that the pointer value is non-NULL. + */ + void CheckNonNull(const void* ptr) { + if (ptr == NULL) { + JniAbortF(function_name_, "non-nullable argument was NULL"); + } + } + + /* + * Verify that the method's return type matches the type of call. + * 'expectedType' will be "L" for all objects, including arrays. + */ + void CheckSig(jmethodID mid, const char* expectedType, bool isStatic) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::AbstractMethod* m = CheckMethodID(mid); + if (m == NULL) { + return; + } + if (*expectedType != MethodHelper(m).GetShorty()[0]) { + JniAbortF(function_name_, "the return type of %s does not match %s", + function_name_, PrettyMethod(m).c_str()); + } + if (isStatic != m->IsStatic()) { + if (isStatic) { + JniAbortF(function_name_, "calling non-static method %s with %s", + PrettyMethod(m).c_str(), function_name_); + } else { + JniAbortF(function_name_, "calling static method %s with %s", + PrettyMethod(m).c_str(), function_name_); + } + } + } + + /* + * Verify that this static field ID is valid for this class. + * + * Assumes "java_class" has already been validated. + */ + void CheckStaticFieldID(jclass java_class, jfieldID fid) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::Class* c = soa_.Decode(java_class); + const mirror::Field* f = CheckFieldID(fid); + if (f == NULL) { + return; + } + if (f->GetDeclaringClass() != c) { + JniAbortF(function_name_, "static jfieldID %p not valid for class %s", + fid, PrettyClass(c).c_str()); + } + } + + /* + * Verify that "mid" is appropriate for "java_class". + * + * A mismatch isn't dangerous, because the jmethodID defines the class. In + * fact, java_class is unused in the implementation. It's best if we don't + * allow bad code in the system though. + * + * Instances of "java_class" must be instances of the method's declaring class. + */ + void CheckStaticMethod(jclass java_class, jmethodID mid) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + const mirror::AbstractMethod* m = CheckMethodID(mid); + if (m == NULL) { + return; + } + mirror::Class* c = soa_.Decode(java_class); + if (!c->IsAssignableFrom(m->GetDeclaringClass())) { + JniAbortF(function_name_, "can't call static %s on class %s", + PrettyMethod(m).c_str(), PrettyClass(c).c_str()); + } + } + + /* + * Verify that "mid" is appropriate for "jobj". + * + * Make sure the object is an instance of the method's declaring class. + * (Note the mid might point to a declaration in an interface; this + * will be handled automatically by the instanceof check.) + */ + void CheckVirtualMethod(jobject java_object, jmethodID mid) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + const mirror::AbstractMethod* m = CheckMethodID(mid); + if (m == NULL) { + return; + } + mirror::Object* o = soa_.Decode(java_object); + if (!o->InstanceOf(m->GetDeclaringClass())) { + JniAbortF(function_name_, "can't call %s on instance of %s", + PrettyMethod(m).c_str(), PrettyTypeOf(o).c_str()); + } + } + + /** + * The format string is a sequence of the following characters, + * and must be followed by arguments of the corresponding types + * in the same order. + * + * Java primitive types: + * B - jbyte + * C - jchar + * D - jdouble + * F - jfloat + * I - jint + * J - jlong + * S - jshort + * Z - jboolean (shown as true and false) + * V - void + * + * Java reference types: + * L - jobject + * a - jarray + * c - jclass + * s - jstring + * + * JNI types: + * b - jboolean (shown as JNI_TRUE and JNI_FALSE) + * f - jfieldID + * m - jmethodID + * p - void* + * r - jint (for release mode arguments) + * u - const char* (Modified UTF-8) + * z - jsize (for lengths; use i if negative values are okay) + * v - JavaVM* + * E - JNIEnv* + * . - no argument; just print "..." (used for varargs JNI calls) + * + * Use the kFlag_NullableUtf flag where 'u' field(s) are nullable. + */ + void Check(bool entry, const char* fmt0, ...) SHARED_LOCKS_REQUIRED (Locks::mutator_lock_) { + va_list ap; + + const mirror::AbstractMethod* traceMethod = NULL; + if (has_method_ && (!soa_.Vm()->trace.empty() || VLOG_IS_ON(third_party_jni))) { + // We need to guard some of the invocation interface's calls: a bad caller might + // use DetachCurrentThread or GetEnv on a thread that's not yet attached. + Thread* self = Thread::Current(); + if ((flags_ & kFlag_Invocation) == 0 || self != NULL) { + traceMethod = self->GetCurrentMethod(NULL); + } + } + + if (((flags_ & kFlag_ForceTrace) != 0) || (traceMethod != NULL && ShouldTrace(soa_.Vm(), traceMethod))) { + va_start(ap, fmt0); + std::string msg; + for (const char* fmt = fmt0; *fmt;) { + char ch = *fmt++; + if (ch == 'B') { // jbyte + jbyte b = va_arg(ap, int); + if (b >= 0 && b < 10) { + StringAppendF(&msg, "%d", b); + } else { + StringAppendF(&msg, "%#x (%d)", b, b); + } + } else if (ch == 'C') { // jchar + jchar c = va_arg(ap, int); + if (c < 0x7f && c >= ' ') { + StringAppendF(&msg, "U+%x ('%c')", c, c); + } else { + StringAppendF(&msg, "U+%x", c); + } + } else if (ch == 'F' || ch == 'D') { // jfloat, jdouble + StringAppendF(&msg, "%g", va_arg(ap, double)); + } else if (ch == 'I' || ch == 'S') { // jint, jshort + StringAppendF(&msg, "%d", va_arg(ap, int)); + } else if (ch == 'J') { // jlong + StringAppendF(&msg, "%lld", va_arg(ap, jlong)); + } else if (ch == 'Z') { // jboolean + StringAppendF(&msg, "%s", va_arg(ap, int) ? "true" : "false"); + } else if (ch == 'V') { // void + msg += "void"; + } else if (ch == 'v') { // JavaVM* + JavaVM* vm = va_arg(ap, JavaVM*); + StringAppendF(&msg, "(JavaVM*)%p", vm); + } else if (ch == 'E') { // JNIEnv* + JNIEnv* env = va_arg(ap, JNIEnv*); + StringAppendF(&msg, "(JNIEnv*)%p", env); + } else if (ch == 'L' || ch == 'a' || ch == 's') { // jobject, jarray, jstring + // For logging purposes, these are identical. + jobject o = va_arg(ap, jobject); + if (o == NULL) { + msg += "NULL"; + } else { + StringAppendF(&msg, "%p", o); + } + } else if (ch == 'b') { // jboolean (JNI-style) + jboolean b = va_arg(ap, int); + msg += (b ? "JNI_TRUE" : "JNI_FALSE"); + } else if (ch == 'c') { // jclass + jclass jc = va_arg(ap, jclass); + mirror::Class* c = reinterpret_cast(Thread::Current()->DecodeJObject(jc)); + if (c == NULL) { + msg += "NULL"; + } else if (c == kInvalidIndirectRefObject || !Runtime::Current()->GetHeap()->IsHeapAddress(c)) { + StringAppendF(&msg, "INVALID POINTER:%p", jc); + } else if (!c->IsClass()) { + msg += "INVALID NON-CLASS OBJECT OF TYPE:" + PrettyTypeOf(c); + } else { + msg += PrettyClass(c); + if (!entry) { + StringAppendF(&msg, " (%p)", jc); + } + } + } else if (ch == 'f') { // jfieldID + jfieldID fid = va_arg(ap, jfieldID); + mirror::Field* f = reinterpret_cast(fid); + msg += PrettyField(f); + if (!entry) { + StringAppendF(&msg, " (%p)", fid); + } + } else if (ch == 'z') { // non-negative jsize + // You might expect jsize to be size_t, but it's not; it's the same as jint. + // We only treat this specially so we can do the non-negative check. + // TODO: maybe this wasn't worth it? + jint i = va_arg(ap, jint); + StringAppendF(&msg, "%d", i); + } else if (ch == 'm') { // jmethodID + jmethodID mid = va_arg(ap, jmethodID); + mirror::AbstractMethod* m = reinterpret_cast(mid); + msg += PrettyMethod(m); + if (!entry) { + StringAppendF(&msg, " (%p)", mid); + } + } else if (ch == 'p') { // void* ("pointer") + void* p = va_arg(ap, void*); + if (p == NULL) { + msg += "NULL"; + } else { + StringAppendF(&msg, "(void*) %p", p); + } + } else if (ch == 'r') { // jint (release mode) + jint releaseMode = va_arg(ap, jint); + if (releaseMode == 0) { + msg += "0"; + } else if (releaseMode == JNI_ABORT) { + msg += "JNI_ABORT"; + } else if (releaseMode == JNI_COMMIT) { + msg += "JNI_COMMIT"; + } else { + StringAppendF(&msg, "invalid release mode %d", releaseMode); + } + } else if (ch == 'u') { // const char* (Modified UTF-8) + const char* utf = va_arg(ap, const char*); + if (utf == NULL) { + msg += "NULL"; + } else { + StringAppendF(&msg, "\"%s\"", utf); + } + } else if (ch == '.') { + msg += "..."; + } else { + JniAbortF(function_name_, "unknown trace format specifier: %c", ch); + return; + } + if (*fmt) { + StringAppendF(&msg, ", "); + } + } + va_end(ap); + + if ((flags_ & kFlag_ForceTrace) != 0) { + LOG(INFO) << "JNI: call to " << function_name_ << "(" << msg << ")"; + } else if (entry) { + if (has_method_) { + std::string methodName(PrettyMethod(traceMethod, false)); + LOG(INFO) << "JNI: " << methodName << " -> " << function_name_ << "(" << msg << ")"; + indent_ = methodName.size() + 1; + } else { + LOG(INFO) << "JNI: -> " << function_name_ << "(" << msg << ")"; + indent_ = 0; + } + } else { + LOG(INFO) << StringPrintf("JNI: %*s<- %s returned %s", indent_, "", function_name_, msg.c_str()); + } + } + + // We always do the thorough checks on entry, and never on exit... + if (entry) { + va_start(ap, fmt0); + for (const char* fmt = fmt0; *fmt; ++fmt) { + char ch = *fmt; + if (ch == 'a') { + CheckArray(va_arg(ap, jarray)); + } else if (ch == 'c') { + CheckInstance(kClass, va_arg(ap, jclass)); + } else if (ch == 'L') { + CheckObject(va_arg(ap, jobject)); + } else if (ch == 'r') { + CheckReleaseMode(va_arg(ap, jint)); + } else if (ch == 's') { + CheckInstance(kString, va_arg(ap, jstring)); + } else if (ch == 'u') { + if ((flags_ & kFlag_Release) != 0) { + CheckNonNull(va_arg(ap, const char*)); + } else { + bool nullable = ((flags_ & kFlag_NullableUtf) != 0); + CheckUtfString(va_arg(ap, const char*), nullable); + } + } else if (ch == 'z') { + CheckLengthPositive(va_arg(ap, jsize)); + } else if (strchr("BCISZbfmpEv", ch) != NULL) { + va_arg(ap, uint32_t); // Skip this argument. + } else if (ch == 'D' || ch == 'F') { + va_arg(ap, double); // Skip this argument. + } else if (ch == 'J') { + va_arg(ap, uint64_t); // Skip this argument. + } else if (ch == '.') { + } else { + LOG(FATAL) << "Unknown check format specifier: " << ch; + } + } + va_end(ap); + } + } + + enum InstanceKind { + kClass, + kDirectByteBuffer, + kObject, + kString, + kThrowable, + }; + + /* + * Verify that "jobj" is a valid non-NULL object reference, and points to + * an instance of expectedClass. + * + * Because we're looking at an object on the GC heap, we have to switch + * to "running" mode before doing the checks. + */ + bool CheckInstance(InstanceKind kind, jobject java_object) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + const char* what = NULL; + switch (kind) { + case kClass: + what = "jclass"; + break; + case kDirectByteBuffer: + what = "direct ByteBuffer"; + break; + case kObject: + what = "jobject"; + break; + case kString: + what = "jstring"; + break; + case kThrowable: + what = "jthrowable"; + break; + default: + LOG(FATAL) << "Unknown kind " << static_cast(kind); + } + + if (java_object == NULL) { + JniAbortF(function_name_, "%s received null %s", function_name_, what); + return false; + } + + mirror::Object* obj = soa_.Decode(java_object); + if (!Runtime::Current()->GetHeap()->IsHeapAddress(obj)) { + Runtime::Current()->GetHeap()->DumpSpaces(); + JniAbortF(function_name_, "%s is an invalid %s: %p (%p)", + what, ToStr(GetIndirectRefKind(java_object)).c_str(), java_object, obj); + return false; + } + + bool okay = true; + switch (kind) { + case kClass: + okay = obj->IsClass(); + break; + case kDirectByteBuffer: + UNIMPLEMENTED(FATAL); + break; + case kString: + okay = obj->GetClass()->IsStringClass(); + break; + case kThrowable: + okay = obj->GetClass()->IsThrowableClass(); + break; + case kObject: + break; + } + if (!okay) { + JniAbortF(function_name_, "%s has wrong type: %s", what, PrettyTypeOf(obj).c_str()); + return false; + } + + return true; + } + + private: + // Set "has_method" to true if we have a valid thread with a method pointer. + // We won't have one before attaching a thread, after detaching a thread, or + // when shutting down the runtime. + void Init(int flags, const char* functionName, bool has_method) { + flags_ = flags; + function_name_ = functionName; + has_method_ = has_method; + } + + /* + * Verify that "array" is non-NULL and points to an Array object. + * + * Since we're dealing with objects, switch to "running" mode. + */ + void CheckArray(jarray java_array) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (java_array == NULL) { + JniAbortF(function_name_, "jarray was NULL"); + return; + } + + mirror::Array* a = soa_.Decode(java_array); + if (!Runtime::Current()->GetHeap()->IsHeapAddress(a)) { + Runtime::Current()->GetHeap()->DumpSpaces(); + JniAbortF(function_name_, "jarray is an invalid %s: %p (%p)", + ToStr(GetIndirectRefKind(java_array)).c_str(), java_array, a); + } else if (!a->IsArrayInstance()) { + JniAbortF(function_name_, "jarray argument has non-array type: %s", PrettyTypeOf(a).c_str()); + } + } + + void CheckLengthPositive(jsize length) { + if (length < 0) { + JniAbortF(function_name_, "negative jsize: %d", length); + } + } + + mirror::Field* CheckFieldID(jfieldID fid) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (fid == NULL) { + JniAbortF(function_name_, "jfieldID was NULL"); + return NULL; + } + mirror::Field* f = soa_.DecodeField(fid); + if (!Runtime::Current()->GetHeap()->IsHeapAddress(f) || !f->IsField()) { + Runtime::Current()->GetHeap()->DumpSpaces(); + JniAbortF(function_name_, "invalid jfieldID: %p", fid); + return NULL; + } + return f; + } + + mirror::AbstractMethod* CheckMethodID(jmethodID mid) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (mid == NULL) { + JniAbortF(function_name_, "jmethodID was NULL"); + return NULL; + } + mirror::AbstractMethod* m = soa_.DecodeMethod(mid); + if (!Runtime::Current()->GetHeap()->IsHeapAddress(m) || !m->IsMethod()) { + Runtime::Current()->GetHeap()->DumpSpaces(); + JniAbortF(function_name_, "invalid jmethodID: %p", mid); + return NULL; + } + return m; + } + + /* + * Verify that "jobj" is a valid object, and that it's an object that JNI + * is allowed to know about. We allow NULL references. + * + * Switches to "running" mode before performing checks. + */ + void CheckObject(jobject java_object) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (java_object == NULL) { + return; + } + + mirror::Object* o = soa_.Decode(java_object); + if (!Runtime::Current()->GetHeap()->IsHeapAddress(o)) { + Runtime::Current()->GetHeap()->DumpSpaces(); + // TODO: when we remove work_around_app_jni_bugs, this should be impossible. + JniAbortF(function_name_, "native code passing in reference to invalid %s: %p", + ToStr(GetIndirectRefKind(java_object)).c_str(), java_object); + } + } + + /* + * Verify that the "mode" argument passed to a primitive array Release + * function is one of the valid values. + */ + void CheckReleaseMode(jint mode) { + if (mode != 0 && mode != JNI_COMMIT && mode != JNI_ABORT) { + JniAbortF(function_name_, "unknown value for release mode: %d", mode); + } + } + + void CheckThread(int flags) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + Thread* self = Thread::Current(); + if (self == NULL) { + JniAbortF(function_name_, "a thread (tid %d) is making JNI calls without being attached", GetTid()); + return; + } + + // Get the *correct* JNIEnv by going through our TLS pointer. + JNIEnvExt* threadEnv = self->GetJniEnv(); + + // Verify that the current thread is (a) attached and (b) associated with + // this particular instance of JNIEnv. + if (soa_.Env() != threadEnv) { + if (soa_.Vm()->work_around_app_jni_bugs) { + // If we're keeping broken code limping along, we need to suppress the abort... + LOG(ERROR) << "APP BUG DETECTED: thread " << *self << " using JNIEnv* from thread " << *soa_.Self(); + } else { + JniAbortF(function_name_, "thread %s using JNIEnv* from thread %s", + ToStr(*self).c_str(), ToStr(*soa_.Self()).c_str()); + return; + } + } + + // Verify that, if this thread previously made a critical "get" call, we + // do the corresponding "release" call before we try anything else. + switch (flags & kFlag_CritMask) { + case kFlag_CritOkay: // okay to call this method + break; + case kFlag_CritBad: // not okay to call + if (threadEnv->critical) { + JniAbortF(function_name_, "thread %s using JNI after critical get", ToStr(*self).c_str()); + return; + } + break; + case kFlag_CritGet: // this is a "get" call + // Don't check here; we allow nested gets. + threadEnv->critical++; + break; + case kFlag_CritRelease: // this is a "release" call + threadEnv->critical--; + if (threadEnv->critical < 0) { + JniAbortF(function_name_, "thread %s called too many critical releases", ToStr(*self).c_str()); + return; + } + break; + default: + LOG(FATAL) << "Bad flags (internal error): " << flags; + } + + // Verify that, if an exception has been raised, the native code doesn't + // make any JNI calls other than the Exception* methods. + if ((flags & kFlag_ExcepOkay) == 0 && self->IsExceptionPending()) { + ThrowLocation throw_location; + mirror::Throwable* exception = self->GetException(&throw_location); + std::string type(PrettyTypeOf(exception)); + // TODO: write native code that doesn't require allocation for dumping an exception. + // TODO: do we care any more? art always dumps pending exceptions on aborting threads. + bool with_stack_trace = (type != "java.lang.OutOfMemoryError"); + if (with_stack_trace) { + JniAbortF(function_name_, "JNI %s called with pending exception '%s' thrown in %s\n%s", + function_name_, type.c_str(), throw_location.Dump().c_str(), + jniGetStackTrace(soa_.Env()).c_str()); + } else { + JniAbortF(function_name_, "JNI %s called with pending exception '%s' thrown in %s", + function_name_, type.c_str(), throw_location.Dump().c_str()); + } + return; + } + } + + // Verifies that "bytes" points to valid Modified UTF-8 data. + void CheckUtfString(const char* bytes, bool nullable) { + if (bytes == NULL) { + if (!nullable) { + JniAbortF(function_name_, "non-nullable const char* was NULL"); + return; + } + return; + } + + const char* errorKind = NULL; + uint8_t utf8 = CheckUtfBytes(bytes, &errorKind); + if (errorKind != NULL) { + JniAbortF(function_name_, + "input is not valid Modified UTF-8: illegal %s byte %#x\n" + " string: '%s'", errorKind, utf8, bytes); + return; + } + } + + static uint8_t CheckUtfBytes(const char* bytes, const char** errorKind) { + while (*bytes != '\0') { + uint8_t utf8 = *(bytes++); + // Switch on the high four bits. + switch (utf8 >> 4) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + // Bit pattern 0xxx. No need for any extra bytes. + break; + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0f: + /* + * Bit pattern 10xx or 1111, which are illegal start bytes. + * Note: 1111 is valid for normal UTF-8, but not the + * Modified UTF-8 used here. + */ + *errorKind = "start"; + return utf8; + case 0x0e: + // Bit pattern 1110, so there are two additional bytes. + utf8 = *(bytes++); + if ((utf8 & 0xc0) != 0x80) { + *errorKind = "continuation"; + return utf8; + } + // Fall through to take care of the final byte. + case 0x0c: + case 0x0d: + // Bit pattern 110x, so there is one additional byte. + utf8 = *(bytes++); + if ((utf8 & 0xc0) != 0x80) { + *errorKind = "continuation"; + return utf8; + } + break; + } + } + return 0; + } + + const ScopedObjectAccess soa_; + const char* function_name_; + int flags_; + bool has_method_; + int indent_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCheck); +}; + +#define CHECK_JNI_ENTRY(flags, types, args...) \ + ScopedCheck sc(env, flags, __FUNCTION__); \ + sc.Check(true, types, ##args) + +#define CHECK_JNI_EXIT(type, exp) ({ \ + typeof(exp) _rc = (exp); \ + sc.Check(false, type, _rc); \ + _rc; }) +#define CHECK_JNI_EXIT_VOID() \ + sc.Check(false, "V") + +/* + * =========================================================================== + * Guarded arrays + * =========================================================================== + */ + +#define kGuardLen 512 /* must be multiple of 2 */ +#define kGuardPattern 0xd5e3 /* uncommon values; d5e3d5e3 invalid addr */ +#define kGuardMagic 0xffd5aa96 + +/* this gets tucked in at the start of the buffer; struct size must be even */ +struct GuardedCopy { + uint32_t magic; + uLong adler; + size_t original_length; + const void* original_ptr; + + /* find the GuardedCopy given the pointer into the "live" data */ + static inline const GuardedCopy* FromData(const void* dataBuf) { + return reinterpret_cast(ActualBuffer(dataBuf)); + } + + /* + * Create an over-sized buffer to hold the contents of "buf". Copy it in, + * filling in the area around it with guard data. + * + * We use a 16-bit pattern to make a rogue memset less likely to elude us. + */ + static void* Create(const void* buf, size_t len, bool modOkay) { + size_t newLen = ActualLength(len); + uint8_t* newBuf = DebugAlloc(newLen); + + // Fill it in with a pattern. + uint16_t* pat = reinterpret_cast(newBuf); + for (size_t i = 0; i < newLen / 2; i++) { + *pat++ = kGuardPattern; + } + + // Copy the data in; note "len" could be zero. + memcpy(newBuf + kGuardLen / 2, buf, len); + + // If modification is not expected, grab a checksum. + uLong adler = 0; + if (!modOkay) { + adler = adler32(0L, Z_NULL, 0); + adler = adler32(adler, reinterpret_cast(buf), len); + *reinterpret_cast(newBuf) = adler; + } + + GuardedCopy* pExtra = reinterpret_cast(newBuf); + pExtra->magic = kGuardMagic; + pExtra->adler = adler; + pExtra->original_ptr = buf; + pExtra->original_length = len; + + return newBuf + kGuardLen / 2; + } + + /* + * Free up the guard buffer, scrub it, and return the original pointer. + */ + static void* Destroy(void* dataBuf) { + const GuardedCopy* pExtra = GuardedCopy::FromData(dataBuf); + void* original_ptr = const_cast(pExtra->original_ptr); + size_t len = pExtra->original_length; + DebugFree(dataBuf, len); + return original_ptr; + } + + /* + * Verify the guard area and, if "modOkay" is false, that the data itself + * has not been altered. + * + * The caller has already checked that "dataBuf" is non-NULL. + */ + static void Check(const char* functionName, const void* dataBuf, bool modOkay) { + static const uint32_t kMagicCmp = kGuardMagic; + const uint8_t* fullBuf = ActualBuffer(dataBuf); + const GuardedCopy* pExtra = GuardedCopy::FromData(dataBuf); + + // Before we do anything with "pExtra", check the magic number. We + // do the check with memcmp rather than "==" in case the pointer is + // unaligned. If it points to completely bogus memory we're going + // to crash, but there's no easy way around that. + if (memcmp(&pExtra->magic, &kMagicCmp, 4) != 0) { + uint8_t buf[4]; + memcpy(buf, &pExtra->magic, 4); + JniAbortF(functionName, + "guard magic does not match (found 0x%02x%02x%02x%02x) -- incorrect data pointer %p?", + buf[3], buf[2], buf[1], buf[0], dataBuf); // Assumes little-endian. + } + + size_t len = pExtra->original_length; + + // Check bottom half of guard; skip over optional checksum storage. + const uint16_t* pat = reinterpret_cast(fullBuf); + for (size_t i = sizeof(GuardedCopy) / 2; i < (kGuardLen / 2 - sizeof(GuardedCopy)) / 2; i++) { + if (pat[i] != kGuardPattern) { + JniAbortF(functionName, "guard pattern(1) disturbed at %p +%d", fullBuf, i*2); + } + } + + int offset = kGuardLen / 2 + len; + if (offset & 0x01) { + // Odd byte; expected value depends on endian. + const uint16_t patSample = kGuardPattern; + uint8_t expected_byte = reinterpret_cast(&patSample)[1]; + if (fullBuf[offset] != expected_byte) { + JniAbortF(functionName, "guard pattern disturbed in odd byte after %p +%d 0x%02x 0x%02x", + fullBuf, offset, fullBuf[offset], expected_byte); + } + offset++; + } + + // Check top half of guard. + pat = reinterpret_cast(fullBuf + offset); + for (size_t i = 0; i < kGuardLen / 4; i++) { + if (pat[i] != kGuardPattern) { + JniAbortF(functionName, "guard pattern(2) disturbed at %p +%d", fullBuf, offset + i*2); + } + } + + // If modification is not expected, verify checksum. Strictly speaking + // this is wrong: if we told the client that we made a copy, there's no + // reason they can't alter the buffer. + if (!modOkay) { + uLong adler = adler32(0L, Z_NULL, 0); + adler = adler32(adler, (const Bytef*)dataBuf, len); + if (pExtra->adler != adler) { + JniAbortF(functionName, "buffer modified (0x%08lx vs 0x%08lx) at address %p", + pExtra->adler, adler, dataBuf); + } + } + } + + private: + static uint8_t* DebugAlloc(size_t len) { + void* result = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); + if (result == MAP_FAILED) { + PLOG(FATAL) << "GuardedCopy::create mmap(" << len << ") failed"; + } + return reinterpret_cast(result); + } + + static void DebugFree(void* dataBuf, size_t len) { + uint8_t* fullBuf = ActualBuffer(dataBuf); + size_t totalByteCount = ActualLength(len); + // TODO: we could mprotect instead, and keep the allocation around for a while. + // This would be even more expensive, but it might catch more errors. + // if (mprotect(fullBuf, totalByteCount, PROT_NONE) != 0) { + // PLOG(WARNING) << "mprotect(PROT_NONE) failed"; + // } + if (munmap(fullBuf, totalByteCount) != 0) { + PLOG(FATAL) << "munmap(" << reinterpret_cast(fullBuf) << ", " << totalByteCount << ") failed"; + } + } + + static const uint8_t* ActualBuffer(const void* dataBuf) { + return reinterpret_cast(dataBuf) - kGuardLen / 2; + } + + static uint8_t* ActualBuffer(void* dataBuf) { + return reinterpret_cast(dataBuf) - kGuardLen / 2; + } + + // Underlying length of a user allocation of 'length' bytes. + static size_t ActualLength(size_t length) { + return (length + kGuardLen + 1) & ~0x01; + } +}; + +/* + * Create a guarded copy of a primitive array. Modifications to the copied + * data are allowed. Returns a pointer to the copied data. + */ +static void* CreateGuardedPACopy(JNIEnv* env, const jarray java_array, jboolean* isCopy) { + ScopedObjectAccess soa(env); + + mirror::Array* a = soa.Decode(java_array); + size_t component_size = a->GetClass()->GetComponentSize(); + size_t byte_count = a->GetLength() * component_size; + void* result = GuardedCopy::Create(a->GetRawData(component_size), byte_count, true); + if (isCopy != NULL) { + *isCopy = JNI_TRUE; + } + return result; +} + +/* + * Perform the array "release" operation, which may or may not copy data + * back into the managed heap, and may or may not release the underlying storage. + */ +static void ReleaseGuardedPACopy(JNIEnv* env, jarray java_array, void* dataBuf, int mode) { + if (reinterpret_cast(dataBuf) == kNoCopyMagic) { + return; + } + + ScopedObjectAccess soa(env); + mirror::Array* a = soa.Decode(java_array); + + GuardedCopy::Check(__FUNCTION__, dataBuf, true); + + if (mode != JNI_ABORT) { + size_t len = GuardedCopy::FromData(dataBuf)->original_length; + memcpy(a->GetRawData(a->GetClass()->GetComponentSize()), dataBuf, len); + } + if (mode != JNI_COMMIT) { + GuardedCopy::Destroy(dataBuf); + } +} + +/* + * =========================================================================== + * JNI functions + * =========================================================================== + */ + +class CheckJNI { + public: + static jint GetVersion(JNIEnv* env) { + CHECK_JNI_ENTRY(kFlag_Default, "E", env); + return CHECK_JNI_EXIT("I", baseEnv(env)->GetVersion(env)); + } + + static jclass DefineClass(JNIEnv* env, const char* name, jobject loader, const jbyte* buf, jsize bufLen) { + CHECK_JNI_ENTRY(kFlag_Default, "EuLpz", env, name, loader, buf, bufLen); + sc.CheckClassName(name); + return CHECK_JNI_EXIT("c", baseEnv(env)->DefineClass(env, name, loader, buf, bufLen)); + } + + static jclass FindClass(JNIEnv* env, const char* name) { + CHECK_JNI_ENTRY(kFlag_Default, "Eu", env, name); + sc.CheckClassName(name); + return CHECK_JNI_EXIT("c", baseEnv(env)->FindClass(env, name)); + } + + static jclass GetSuperclass(JNIEnv* env, jclass c) { + CHECK_JNI_ENTRY(kFlag_Default, "Ec", env, c); + return CHECK_JNI_EXIT("c", baseEnv(env)->GetSuperclass(env, c)); + } + + static jboolean IsAssignableFrom(JNIEnv* env, jclass c1, jclass c2) { + CHECK_JNI_ENTRY(kFlag_Default, "Ecc", env, c1, c2); + return CHECK_JNI_EXIT("b", baseEnv(env)->IsAssignableFrom(env, c1, c2)); + } + + static jmethodID FromReflectedMethod(JNIEnv* env, jobject method) { + CHECK_JNI_ENTRY(kFlag_Default, "EL", env, method); + // TODO: check that 'field' is a java.lang.reflect.Method. + return CHECK_JNI_EXIT("m", baseEnv(env)->FromReflectedMethod(env, method)); + } + + static jfieldID FromReflectedField(JNIEnv* env, jobject field) { + CHECK_JNI_ENTRY(kFlag_Default, "EL", env, field); + // TODO: check that 'field' is a java.lang.reflect.Field. + return CHECK_JNI_EXIT("f", baseEnv(env)->FromReflectedField(env, field)); + } + + static jobject ToReflectedMethod(JNIEnv* env, jclass cls, jmethodID mid, jboolean isStatic) { + CHECK_JNI_ENTRY(kFlag_Default, "Ecmb", env, cls, mid, isStatic); + return CHECK_JNI_EXIT("L", baseEnv(env)->ToReflectedMethod(env, cls, mid, isStatic)); + } + + static jobject ToReflectedField(JNIEnv* env, jclass cls, jfieldID fid, jboolean isStatic) { + CHECK_JNI_ENTRY(kFlag_Default, "Ecfb", env, cls, fid, isStatic); + return CHECK_JNI_EXIT("L", baseEnv(env)->ToReflectedField(env, cls, fid, isStatic)); + } + + static jint Throw(JNIEnv* env, jthrowable obj) { + CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj); + // TODO: check that 'obj' is a java.lang.Throwable. + return CHECK_JNI_EXIT("I", baseEnv(env)->Throw(env, obj)); + } + + static jint ThrowNew(JNIEnv* env, jclass c, const char* message) { + CHECK_JNI_ENTRY(kFlag_NullableUtf, "Ecu", env, c, message); + return CHECK_JNI_EXIT("I", baseEnv(env)->ThrowNew(env, c, message)); + } + + static jthrowable ExceptionOccurred(JNIEnv* env) { + CHECK_JNI_ENTRY(kFlag_ExcepOkay, "E", env); + return CHECK_JNI_EXIT("L", baseEnv(env)->ExceptionOccurred(env)); + } + + static void ExceptionDescribe(JNIEnv* env) { + CHECK_JNI_ENTRY(kFlag_ExcepOkay, "E", env); + baseEnv(env)->ExceptionDescribe(env); + CHECK_JNI_EXIT_VOID(); + } + + static void ExceptionClear(JNIEnv* env) { + CHECK_JNI_ENTRY(kFlag_ExcepOkay, "E", env); + baseEnv(env)->ExceptionClear(env); + CHECK_JNI_EXIT_VOID(); + } + + static void FatalError(JNIEnv* env, const char* msg) { + CHECK_JNI_ENTRY(kFlag_NullableUtf, "Eu", env, msg); + baseEnv(env)->FatalError(env, msg); + CHECK_JNI_EXIT_VOID(); + } + + static jint PushLocalFrame(JNIEnv* env, jint capacity) { + CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EI", env, capacity); + return CHECK_JNI_EXIT("I", baseEnv(env)->PushLocalFrame(env, capacity)); + } + + static jobject PopLocalFrame(JNIEnv* env, jobject res) { + CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, res); + return CHECK_JNI_EXIT("L", baseEnv(env)->PopLocalFrame(env, res)); + } + + static jobject NewGlobalRef(JNIEnv* env, jobject obj) { + CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj); + return CHECK_JNI_EXIT("L", baseEnv(env)->NewGlobalRef(env, obj)); + } + + static jobject NewLocalRef(JNIEnv* env, jobject ref) { + CHECK_JNI_ENTRY(kFlag_Default, "EL", env, ref); + return CHECK_JNI_EXIT("L", baseEnv(env)->NewLocalRef(env, ref)); + } + + static void DeleteGlobalRef(JNIEnv* env, jobject globalRef) { + CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, globalRef); + if (globalRef != NULL && GetIndirectRefKind(globalRef) != kGlobal) { + JniAbortF(__FUNCTION__, "DeleteGlobalRef on %s: %p", + ToStr(GetIndirectRefKind(globalRef)).c_str(), globalRef); + } else { + baseEnv(env)->DeleteGlobalRef(env, globalRef); + CHECK_JNI_EXIT_VOID(); + } + } + + static void DeleteWeakGlobalRef(JNIEnv* env, jweak weakGlobalRef) { + CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, weakGlobalRef); + if (weakGlobalRef != NULL && GetIndirectRefKind(weakGlobalRef) != kWeakGlobal) { + JniAbortF(__FUNCTION__, "DeleteWeakGlobalRef on %s: %p", + ToStr(GetIndirectRefKind(weakGlobalRef)).c_str(), weakGlobalRef); + } else { + baseEnv(env)->DeleteWeakGlobalRef(env, weakGlobalRef); + CHECK_JNI_EXIT_VOID(); + } + } + + static void DeleteLocalRef(JNIEnv* env, jobject localRef) { + CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, localRef); + if (localRef != NULL && GetIndirectRefKind(localRef) != kLocal && !IsSirtLocalRef(env, localRef)) { + JniAbortF(__FUNCTION__, "DeleteLocalRef on %s: %p", + ToStr(GetIndirectRefKind(localRef)).c_str(), localRef); + } else { + baseEnv(env)->DeleteLocalRef(env, localRef); + CHECK_JNI_EXIT_VOID(); + } + } + + static jint EnsureLocalCapacity(JNIEnv *env, jint capacity) { + CHECK_JNI_ENTRY(kFlag_Default, "EI", env, capacity); + return CHECK_JNI_EXIT("I", baseEnv(env)->EnsureLocalCapacity(env, capacity)); + } + + static jboolean IsSameObject(JNIEnv* env, jobject ref1, jobject ref2) { + CHECK_JNI_ENTRY(kFlag_Default, "ELL", env, ref1, ref2); + return CHECK_JNI_EXIT("b", baseEnv(env)->IsSameObject(env, ref1, ref2)); + } + + static jobject AllocObject(JNIEnv* env, jclass c) { + CHECK_JNI_ENTRY(kFlag_Default, "Ec", env, c); + return CHECK_JNI_EXIT("L", baseEnv(env)->AllocObject(env, c)); + } + + static jobject NewObject(JNIEnv* env, jclass c, jmethodID mid, ...) { + CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); + va_list args; + va_start(args, mid); + jobject result = baseEnv(env)->NewObjectV(env, c, mid, args); + va_end(args); + return CHECK_JNI_EXIT("L", result); + } + + static jobject NewObjectV(JNIEnv* env, jclass c, jmethodID mid, va_list args) { + CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); + return CHECK_JNI_EXIT("L", baseEnv(env)->NewObjectV(env, c, mid, args)); + } + + static jobject NewObjectA(JNIEnv* env, jclass c, jmethodID mid, jvalue* args) { + CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); + return CHECK_JNI_EXIT("L", baseEnv(env)->NewObjectA(env, c, mid, args)); + } + + static jclass GetObjectClass(JNIEnv* env, jobject obj) { + CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj); + return CHECK_JNI_EXIT("c", baseEnv(env)->GetObjectClass(env, obj)); + } + + static jboolean IsInstanceOf(JNIEnv* env, jobject obj, jclass c) { + CHECK_JNI_ENTRY(kFlag_Default, "ELc", env, obj, c); + return CHECK_JNI_EXIT("b", baseEnv(env)->IsInstanceOf(env, obj, c)); + } + + static jmethodID GetMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) { + CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig); + return CHECK_JNI_EXIT("m", baseEnv(env)->GetMethodID(env, c, name, sig)); + } + + static jfieldID GetFieldID(JNIEnv* env, jclass c, const char* name, const char* sig) { + CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig); + return CHECK_JNI_EXIT("f", baseEnv(env)->GetFieldID(env, c, name, sig)); + } + + static jmethodID GetStaticMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) { + CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig); + return CHECK_JNI_EXIT("m", baseEnv(env)->GetStaticMethodID(env, c, name, sig)); + } + + static jfieldID GetStaticFieldID(JNIEnv* env, jclass c, const char* name, const char* sig) { + CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig); + return CHECK_JNI_EXIT("f", baseEnv(env)->GetStaticFieldID(env, c, name, sig)); + } + +#define FIELD_ACCESSORS(_ctype, _jname, _type) \ + static _ctype GetStatic##_jname##Field(JNIEnv* env, jclass c, jfieldID fid) { \ + CHECK_JNI_ENTRY(kFlag_Default, "Ecf", env, c, fid); \ + sc.CheckStaticFieldID(c, fid); \ + return CHECK_JNI_EXIT(_type, baseEnv(env)->GetStatic##_jname##Field(env, c, fid)); \ + } \ + static _ctype Get##_jname##Field(JNIEnv* env, jobject obj, jfieldID fid) { \ + CHECK_JNI_ENTRY(kFlag_Default, "ELf", env, obj, fid); \ + sc.CheckInstanceFieldID(obj, fid); \ + return CHECK_JNI_EXIT(_type, baseEnv(env)->Get##_jname##Field(env, obj, fid)); \ + } \ + static void SetStatic##_jname##Field(JNIEnv* env, jclass c, jfieldID fid, _ctype value) { \ + CHECK_JNI_ENTRY(kFlag_Default, "Ecf" _type, env, c, fid, value); \ + sc.CheckStaticFieldID(c, fid); \ + /* "value" arg only used when type == ref */ \ + sc.CheckFieldType((jobject)(uint32_t)value, fid, _type[0], true); \ + baseEnv(env)->SetStatic##_jname##Field(env, c, fid, value); \ + CHECK_JNI_EXIT_VOID(); \ + } \ + static void Set##_jname##Field(JNIEnv* env, jobject obj, jfieldID fid, _ctype value) { \ + CHECK_JNI_ENTRY(kFlag_Default, "ELf" _type, env, obj, fid, value); \ + sc.CheckInstanceFieldID(obj, fid); \ + /* "value" arg only used when type == ref */ \ + sc.CheckFieldType((jobject)(uint32_t) value, fid, _type[0], false); \ + baseEnv(env)->Set##_jname##Field(env, obj, fid, value); \ + CHECK_JNI_EXIT_VOID(); \ + } + +FIELD_ACCESSORS(jobject, Object, "L"); +FIELD_ACCESSORS(jboolean, Boolean, "Z"); +FIELD_ACCESSORS(jbyte, Byte, "B"); +FIELD_ACCESSORS(jchar, Char, "C"); +FIELD_ACCESSORS(jshort, Short, "S"); +FIELD_ACCESSORS(jint, Int, "I"); +FIELD_ACCESSORS(jlong, Long, "J"); +FIELD_ACCESSORS(jfloat, Float, "F"); +FIELD_ACCESSORS(jdouble, Double, "D"); + +#define CALL(_ctype, _jname, _retdecl, _retasgn, _retok, _retsig) \ + /* Virtual... */ \ + static _ctype Call##_jname##Method(JNIEnv* env, jobject obj, \ + jmethodID mid, ...) \ + { \ + CHECK_JNI_ENTRY(kFlag_Default, "ELm.", env, obj, mid); /* TODO: args! */ \ + sc.CheckSig(mid, _retsig, false); \ + sc.CheckVirtualMethod(obj, mid); \ + _retdecl; \ + va_list args; \ + va_start(args, mid); \ + _retasgn(baseEnv(env)->Call##_jname##MethodV(env, obj, mid, args)); \ + va_end(args); \ + _retok; \ + } \ + static _ctype Call##_jname##MethodV(JNIEnv* env, jobject obj, \ + jmethodID mid, va_list args) \ + { \ + CHECK_JNI_ENTRY(kFlag_Default, "ELm.", env, obj, mid); /* TODO: args! */ \ + sc.CheckSig(mid, _retsig, false); \ + sc.CheckVirtualMethod(obj, mid); \ + _retdecl; \ + _retasgn(baseEnv(env)->Call##_jname##MethodV(env, obj, mid, args)); \ + _retok; \ + } \ + static _ctype Call##_jname##MethodA(JNIEnv* env, jobject obj, \ + jmethodID mid, jvalue* args) \ + { \ + CHECK_JNI_ENTRY(kFlag_Default, "ELm.", env, obj, mid); /* TODO: args! */ \ + sc.CheckSig(mid, _retsig, false); \ + sc.CheckVirtualMethod(obj, mid); \ + _retdecl; \ + _retasgn(baseEnv(env)->Call##_jname##MethodA(env, obj, mid, args)); \ + _retok; \ + } \ + /* Non-virtual... */ \ + static _ctype CallNonvirtual##_jname##Method(JNIEnv* env, \ + jobject obj, jclass c, jmethodID mid, ...) \ + { \ + CHECK_JNI_ENTRY(kFlag_Default, "ELcm.", env, obj, c, mid); /* TODO: args! */ \ + sc.CheckSig(mid, _retsig, false); \ + sc.CheckVirtualMethod(obj, mid); \ + _retdecl; \ + va_list args; \ + va_start(args, mid); \ + _retasgn(baseEnv(env)->CallNonvirtual##_jname##MethodV(env, obj, c, mid, args)); \ + va_end(args); \ + _retok; \ + } \ + static _ctype CallNonvirtual##_jname##MethodV(JNIEnv* env, \ + jobject obj, jclass c, jmethodID mid, va_list args) \ + { \ + CHECK_JNI_ENTRY(kFlag_Default, "ELcm.", env, obj, c, mid); /* TODO: args! */ \ + sc.CheckSig(mid, _retsig, false); \ + sc.CheckVirtualMethod(obj, mid); \ + _retdecl; \ + _retasgn(baseEnv(env)->CallNonvirtual##_jname##MethodV(env, obj, c, mid, args)); \ + _retok; \ + } \ + static _ctype CallNonvirtual##_jname##MethodA(JNIEnv* env, \ + jobject obj, jclass c, jmethodID mid, jvalue* args) \ + { \ + CHECK_JNI_ENTRY(kFlag_Default, "ELcm.", env, obj, c, mid); /* TODO: args! */ \ + sc.CheckSig(mid, _retsig, false); \ + sc.CheckVirtualMethod(obj, mid); \ + _retdecl; \ + _retasgn(baseEnv(env)->CallNonvirtual##_jname##MethodA(env, obj, c, mid, args)); \ + _retok; \ + } \ + /* Static... */ \ + static _ctype CallStatic##_jname##Method(JNIEnv* env, jclass c, jmethodID mid, ...) \ + { \ + CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); /* TODO: args! */ \ + sc.CheckSig(mid, _retsig, true); \ + sc.CheckStaticMethod(c, mid); \ + _retdecl; \ + va_list args; \ + va_start(args, mid); \ + _retasgn(baseEnv(env)->CallStatic##_jname##MethodV(env, c, mid, args)); \ + va_end(args); \ + _retok; \ + } \ + static _ctype CallStatic##_jname##MethodV(JNIEnv* env, jclass c, jmethodID mid, va_list args) \ + { \ + CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); /* TODO: args! */ \ + sc.CheckSig(mid, _retsig, true); \ + sc.CheckStaticMethod(c, mid); \ + _retdecl; \ + _retasgn(baseEnv(env)->CallStatic##_jname##MethodV(env, c, mid, args)); \ + _retok; \ + } \ + static _ctype CallStatic##_jname##MethodA(JNIEnv* env, jclass c, jmethodID mid, jvalue* args) \ + { \ + CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); /* TODO: args! */ \ + sc.CheckSig(mid, _retsig, true); \ + sc.CheckStaticMethod(c, mid); \ + _retdecl; \ + _retasgn(baseEnv(env)->CallStatic##_jname##MethodA(env, c, mid, args)); \ + _retok; \ + } + +#define NON_VOID_RETURN(_retsig, _ctype) return CHECK_JNI_EXIT(_retsig, (_ctype) result) +#define VOID_RETURN CHECK_JNI_EXIT_VOID() + +CALL(jobject, Object, mirror::Object* result, result = reinterpret_cast, NON_VOID_RETURN("L", jobject), "L"); +CALL(jboolean, Boolean, jboolean result, result =, NON_VOID_RETURN("Z", jboolean), "Z"); +CALL(jbyte, Byte, jbyte result, result =, NON_VOID_RETURN("B", jbyte), "B"); +CALL(jchar, Char, jchar result, result =, NON_VOID_RETURN("C", jchar), "C"); +CALL(jshort, Short, jshort result, result =, NON_VOID_RETURN("S", jshort), "S"); +CALL(jint, Int, jint result, result =, NON_VOID_RETURN("I", jint), "I"); +CALL(jlong, Long, jlong result, result =, NON_VOID_RETURN("J", jlong), "J"); +CALL(jfloat, Float, jfloat result, result =, NON_VOID_RETURN("F", jfloat), "F"); +CALL(jdouble, Double, jdouble result, result =, NON_VOID_RETURN("D", jdouble), "D"); +CALL(void, Void, , , VOID_RETURN, "V"); + + static jstring NewString(JNIEnv* env, const jchar* unicodeChars, jsize len) { + CHECK_JNI_ENTRY(kFlag_Default, "Epz", env, unicodeChars, len); + return CHECK_JNI_EXIT("s", baseEnv(env)->NewString(env, unicodeChars, len)); + } + + static jsize GetStringLength(JNIEnv* env, jstring string) { + CHECK_JNI_ENTRY(kFlag_CritOkay, "Es", env, string); + return CHECK_JNI_EXIT("I", baseEnv(env)->GetStringLength(env, string)); + } + + static const jchar* GetStringChars(JNIEnv* env, jstring java_string, jboolean* isCopy) { + CHECK_JNI_ENTRY(kFlag_CritOkay, "Esp", env, java_string, isCopy); + const jchar* result = baseEnv(env)->GetStringChars(env, java_string, isCopy); + if (sc.ForceCopy() && result != NULL) { + mirror::String* s = sc.soa().Decode(java_string); + int byteCount = s->GetLength() * 2; + result = (const jchar*) GuardedCopy::Create(result, byteCount, false); + if (isCopy != NULL) { + *isCopy = JNI_TRUE; + } + } + return CHECK_JNI_EXIT("p", result); + } + + static void ReleaseStringChars(JNIEnv* env, jstring string, const jchar* chars) { + CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "Esp", env, string, chars); + sc.CheckNonNull(chars); + if (sc.ForceCopy()) { + GuardedCopy::Check(__FUNCTION__, chars, false); + chars = reinterpret_cast(GuardedCopy::Destroy(const_cast(chars))); + } + baseEnv(env)->ReleaseStringChars(env, string, chars); + CHECK_JNI_EXIT_VOID(); + } + + static jstring NewStringUTF(JNIEnv* env, const char* bytes) { + CHECK_JNI_ENTRY(kFlag_NullableUtf, "Eu", env, bytes); // TODO: show pointer and truncate string. + return CHECK_JNI_EXIT("s", baseEnv(env)->NewStringUTF(env, bytes)); + } + + static jsize GetStringUTFLength(JNIEnv* env, jstring string) { + CHECK_JNI_ENTRY(kFlag_CritOkay, "Es", env, string); + return CHECK_JNI_EXIT("I", baseEnv(env)->GetStringUTFLength(env, string)); + } + + static const char* GetStringUTFChars(JNIEnv* env, jstring string, jboolean* isCopy) { + CHECK_JNI_ENTRY(kFlag_CritOkay, "Esp", env, string, isCopy); + const char* result = baseEnv(env)->GetStringUTFChars(env, string, isCopy); + if (sc.ForceCopy() && result != NULL) { + result = (const char*) GuardedCopy::Create(result, strlen(result) + 1, false); + if (isCopy != NULL) { + *isCopy = JNI_TRUE; + } + } + return CHECK_JNI_EXIT("u", result); // TODO: show pointer and truncate string. + } + + static void ReleaseStringUTFChars(JNIEnv* env, jstring string, const char* utf) { + CHECK_JNI_ENTRY(kFlag_ExcepOkay | kFlag_Release, "Esu", env, string, utf); // TODO: show pointer and truncate string. + if (sc.ForceCopy()) { + GuardedCopy::Check(__FUNCTION__, utf, false); + utf = reinterpret_cast(GuardedCopy::Destroy(const_cast(utf))); + } + baseEnv(env)->ReleaseStringUTFChars(env, string, utf); + CHECK_JNI_EXIT_VOID(); + } + + static jsize GetArrayLength(JNIEnv* env, jarray array) { + CHECK_JNI_ENTRY(kFlag_CritOkay, "Ea", env, array); + return CHECK_JNI_EXIT("I", baseEnv(env)->GetArrayLength(env, array)); + } + + static jobjectArray NewObjectArray(JNIEnv* env, jsize length, jclass elementClass, jobject initialElement) { + CHECK_JNI_ENTRY(kFlag_Default, "EzcL", env, length, elementClass, initialElement); + return CHECK_JNI_EXIT("a", baseEnv(env)->NewObjectArray(env, length, elementClass, initialElement)); + } + + static jobject GetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index) { + CHECK_JNI_ENTRY(kFlag_Default, "EaI", env, array, index); + return CHECK_JNI_EXIT("L", baseEnv(env)->GetObjectArrayElement(env, array, index)); + } + + static void SetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index, jobject value) { + CHECK_JNI_ENTRY(kFlag_Default, "EaIL", env, array, index, value); + baseEnv(env)->SetObjectArrayElement(env, array, index, value); + CHECK_JNI_EXIT_VOID(); + } + +#define NEW_PRIMITIVE_ARRAY(_artype, _jname) \ + static _artype New##_jname##Array(JNIEnv* env, jsize length) { \ + CHECK_JNI_ENTRY(kFlag_Default, "Ez", env, length); \ + return CHECK_JNI_EXIT("a", baseEnv(env)->New##_jname##Array(env, length)); \ + } +NEW_PRIMITIVE_ARRAY(jbooleanArray, Boolean); +NEW_PRIMITIVE_ARRAY(jbyteArray, Byte); +NEW_PRIMITIVE_ARRAY(jcharArray, Char); +NEW_PRIMITIVE_ARRAY(jshortArray, Short); +NEW_PRIMITIVE_ARRAY(jintArray, Int); +NEW_PRIMITIVE_ARRAY(jlongArray, Long); +NEW_PRIMITIVE_ARRAY(jfloatArray, Float); +NEW_PRIMITIVE_ARRAY(jdoubleArray, Double); + +struct ForceCopyGetChecker { + public: + ForceCopyGetChecker(ScopedCheck& sc, jboolean* isCopy) { + force_copy = sc.ForceCopy(); + no_copy = 0; + if (force_copy && isCopy != NULL) { + // Capture this before the base call tramples on it. + no_copy = *reinterpret_cast(isCopy); + } + } + + template + ResultT Check(JNIEnv* env, jarray array, jboolean* isCopy, ResultT result) { + if (force_copy && result != NULL) { + if (no_copy != kNoCopyMagic) { + result = reinterpret_cast(CreateGuardedPACopy(env, array, isCopy)); + } + } + return result; + } + + uint32_t no_copy; + bool force_copy; +}; + +#define GET_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname) \ + static _ctype* Get##_jname##ArrayElements(JNIEnv* env, _ctype##Array array, jboolean* isCopy) { \ + CHECK_JNI_ENTRY(kFlag_Default, "Eap", env, array, isCopy); \ + _ctype* result = ForceCopyGetChecker(sc, isCopy).Check(env, array, isCopy, baseEnv(env)->Get##_jname##ArrayElements(env, array, isCopy)); \ + return CHECK_JNI_EXIT("p", result); \ + } + +#define RELEASE_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname) \ + static void Release##_jname##ArrayElements(JNIEnv* env, _ctype##Array array, _ctype* elems, jint mode) { \ + CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "Eapr", env, array, elems, mode); \ + sc.CheckNonNull(elems); \ + if (sc.ForceCopy()) { \ + ReleaseGuardedPACopy(env, array, elems, mode); \ + } \ + baseEnv(env)->Release##_jname##ArrayElements(env, array, elems, mode); \ + CHECK_JNI_EXIT_VOID(); \ + } + +#define GET_PRIMITIVE_ARRAY_REGION(_ctype, _jname) \ + static void Get##_jname##ArrayRegion(JNIEnv* env, _ctype##Array array, jsize start, jsize len, _ctype* buf) { \ + CHECK_JNI_ENTRY(kFlag_Default, "EaIIp", env, array, start, len, buf); \ + baseEnv(env)->Get##_jname##ArrayRegion(env, array, start, len, buf); \ + CHECK_JNI_EXIT_VOID(); \ + } + +#define SET_PRIMITIVE_ARRAY_REGION(_ctype, _jname) \ + static void Set##_jname##ArrayRegion(JNIEnv* env, _ctype##Array array, jsize start, jsize len, const _ctype* buf) { \ + CHECK_JNI_ENTRY(kFlag_Default, "EaIIp", env, array, start, len, buf); \ + baseEnv(env)->Set##_jname##ArrayRegion(env, array, start, len, buf); \ + CHECK_JNI_EXIT_VOID(); \ + } + +#define PRIMITIVE_ARRAY_FUNCTIONS(_ctype, _jname, _typechar) \ + GET_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname); \ + RELEASE_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname); \ + GET_PRIMITIVE_ARRAY_REGION(_ctype, _jname); \ + SET_PRIMITIVE_ARRAY_REGION(_ctype, _jname); + +// TODO: verify primitive array type matches call type. +PRIMITIVE_ARRAY_FUNCTIONS(jboolean, Boolean, 'Z'); +PRIMITIVE_ARRAY_FUNCTIONS(jbyte, Byte, 'B'); +PRIMITIVE_ARRAY_FUNCTIONS(jchar, Char, 'C'); +PRIMITIVE_ARRAY_FUNCTIONS(jshort, Short, 'S'); +PRIMITIVE_ARRAY_FUNCTIONS(jint, Int, 'I'); +PRIMITIVE_ARRAY_FUNCTIONS(jlong, Long, 'J'); +PRIMITIVE_ARRAY_FUNCTIONS(jfloat, Float, 'F'); +PRIMITIVE_ARRAY_FUNCTIONS(jdouble, Double, 'D'); + + static jint RegisterNatives(JNIEnv* env, jclass c, const JNINativeMethod* methods, jint nMethods) { + CHECK_JNI_ENTRY(kFlag_Default, "EcpI", env, c, methods, nMethods); + return CHECK_JNI_EXIT("I", baseEnv(env)->RegisterNatives(env, c, methods, nMethods)); + } + + static jint UnregisterNatives(JNIEnv* env, jclass c) { + CHECK_JNI_ENTRY(kFlag_Default, "Ec", env, c); + return CHECK_JNI_EXIT("I", baseEnv(env)->UnregisterNatives(env, c)); + } + + static jint MonitorEnter(JNIEnv* env, jobject obj) { + CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj); + if (!sc.CheckInstance(ScopedCheck::kObject, obj)) { + return JNI_ERR; // Only for jni_internal_test. Real code will have aborted already. + } + return CHECK_JNI_EXIT("I", baseEnv(env)->MonitorEnter(env, obj)); + } + + static jint MonitorExit(JNIEnv* env, jobject obj) { + CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, obj); + if (!sc.CheckInstance(ScopedCheck::kObject, obj)) { + return JNI_ERR; // Only for jni_internal_test. Real code will have aborted already. + } + return CHECK_JNI_EXIT("I", baseEnv(env)->MonitorExit(env, obj)); + } + + static jint GetJavaVM(JNIEnv *env, JavaVM **vm) { + CHECK_JNI_ENTRY(kFlag_Default, "Ep", env, vm); + return CHECK_JNI_EXIT("I", baseEnv(env)->GetJavaVM(env, vm)); + } + + static void GetStringRegion(JNIEnv* env, jstring str, jsize start, jsize len, jchar* buf) { + CHECK_JNI_ENTRY(kFlag_CritOkay, "EsIIp", env, str, start, len, buf); + baseEnv(env)->GetStringRegion(env, str, start, len, buf); + CHECK_JNI_EXIT_VOID(); + } + + static void GetStringUTFRegion(JNIEnv* env, jstring str, jsize start, jsize len, char* buf) { + CHECK_JNI_ENTRY(kFlag_CritOkay, "EsIIp", env, str, start, len, buf); + baseEnv(env)->GetStringUTFRegion(env, str, start, len, buf); + CHECK_JNI_EXIT_VOID(); + } + + static void* GetPrimitiveArrayCritical(JNIEnv* env, jarray array, jboolean* isCopy) { + CHECK_JNI_ENTRY(kFlag_CritGet, "Eap", env, array, isCopy); + void* result = baseEnv(env)->GetPrimitiveArrayCritical(env, array, isCopy); + if (sc.ForceCopy() && result != NULL) { + result = CreateGuardedPACopy(env, array, isCopy); + } + return CHECK_JNI_EXIT("p", result); + } + + static void ReleasePrimitiveArrayCritical(JNIEnv* env, jarray array, void* carray, jint mode) { + CHECK_JNI_ENTRY(kFlag_CritRelease | kFlag_ExcepOkay, "Eapr", env, array, carray, mode); + sc.CheckNonNull(carray); + if (sc.ForceCopy()) { + ReleaseGuardedPACopy(env, array, carray, mode); + } + baseEnv(env)->ReleasePrimitiveArrayCritical(env, array, carray, mode); + CHECK_JNI_EXIT_VOID(); + } + + static const jchar* GetStringCritical(JNIEnv* env, jstring java_string, jboolean* isCopy) { + CHECK_JNI_ENTRY(kFlag_CritGet, "Esp", env, java_string, isCopy); + const jchar* result = baseEnv(env)->GetStringCritical(env, java_string, isCopy); + if (sc.ForceCopy() && result != NULL) { + mirror::String* s = sc.soa().Decode(java_string); + int byteCount = s->GetLength() * 2; + result = (const jchar*) GuardedCopy::Create(result, byteCount, false); + if (isCopy != NULL) { + *isCopy = JNI_TRUE; + } + } + return CHECK_JNI_EXIT("p", result); + } + + static void ReleaseStringCritical(JNIEnv* env, jstring string, const jchar* carray) { + CHECK_JNI_ENTRY(kFlag_CritRelease | kFlag_ExcepOkay, "Esp", env, string, carray); + sc.CheckNonNull(carray); + if (sc.ForceCopy()) { + GuardedCopy::Check(__FUNCTION__, carray, false); + carray = reinterpret_cast(GuardedCopy::Destroy(const_cast(carray))); + } + baseEnv(env)->ReleaseStringCritical(env, string, carray); + CHECK_JNI_EXIT_VOID(); + } + + static jweak NewWeakGlobalRef(JNIEnv* env, jobject obj) { + CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj); + return CHECK_JNI_EXIT("L", baseEnv(env)->NewWeakGlobalRef(env, obj)); + } + + static jboolean ExceptionCheck(JNIEnv* env) { + CHECK_JNI_ENTRY(kFlag_CritOkay | kFlag_ExcepOkay, "E", env); + return CHECK_JNI_EXIT("b", baseEnv(env)->ExceptionCheck(env)); + } + + static jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj) { + // Note: we use "Ep" rather than "EL" because this is the one JNI function + // that it's okay to pass an invalid reference to. + CHECK_JNI_ENTRY(kFlag_Default, "Ep", env, obj); + // TODO: proper decoding of jobjectRefType! + return CHECK_JNI_EXIT("I", baseEnv(env)->GetObjectRefType(env, obj)); + } + + static jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) { + CHECK_JNI_ENTRY(kFlag_Default, "EpJ", env, address, capacity); + if (address == NULL) { + JniAbortF(__FUNCTION__, "non-nullable address is NULL"); + } + if (capacity <= 0) { + JniAbortF(__FUNCTION__, "capacity must be greater than 0: %lld", capacity); + } + return CHECK_JNI_EXIT("L", baseEnv(env)->NewDirectByteBuffer(env, address, capacity)); + } + + static void* GetDirectBufferAddress(JNIEnv* env, jobject buf) { + CHECK_JNI_ENTRY(kFlag_Default, "EL", env, buf); + // TODO: check that 'buf' is a java.nio.Buffer. + return CHECK_JNI_EXIT("p", baseEnv(env)->GetDirectBufferAddress(env, buf)); + } + + static jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf) { + CHECK_JNI_ENTRY(kFlag_Default, "EL", env, buf); + // TODO: check that 'buf' is a java.nio.Buffer. + return CHECK_JNI_EXIT("J", baseEnv(env)->GetDirectBufferCapacity(env, buf)); + } + + private: + static inline const JNINativeInterface* baseEnv(JNIEnv* env) { + return reinterpret_cast(env)->unchecked_functions; + } +}; + +const JNINativeInterface gCheckNativeInterface = { + NULL, // reserved0. + NULL, // reserved1. + NULL, // reserved2. + NULL, // reserved3. + CheckJNI::GetVersion, + CheckJNI::DefineClass, + CheckJNI::FindClass, + CheckJNI::FromReflectedMethod, + CheckJNI::FromReflectedField, + CheckJNI::ToReflectedMethod, + CheckJNI::GetSuperclass, + CheckJNI::IsAssignableFrom, + CheckJNI::ToReflectedField, + CheckJNI::Throw, + CheckJNI::ThrowNew, + CheckJNI::ExceptionOccurred, + CheckJNI::ExceptionDescribe, + CheckJNI::ExceptionClear, + CheckJNI::FatalError, + CheckJNI::PushLocalFrame, + CheckJNI::PopLocalFrame, + CheckJNI::NewGlobalRef, + CheckJNI::DeleteGlobalRef, + CheckJNI::DeleteLocalRef, + CheckJNI::IsSameObject, + CheckJNI::NewLocalRef, + CheckJNI::EnsureLocalCapacity, + CheckJNI::AllocObject, + CheckJNI::NewObject, + CheckJNI::NewObjectV, + CheckJNI::NewObjectA, + CheckJNI::GetObjectClass, + CheckJNI::IsInstanceOf, + CheckJNI::GetMethodID, + CheckJNI::CallObjectMethod, + CheckJNI::CallObjectMethodV, + CheckJNI::CallObjectMethodA, + CheckJNI::CallBooleanMethod, + CheckJNI::CallBooleanMethodV, + CheckJNI::CallBooleanMethodA, + CheckJNI::CallByteMethod, + CheckJNI::CallByteMethodV, + CheckJNI::CallByteMethodA, + CheckJNI::CallCharMethod, + CheckJNI::CallCharMethodV, + CheckJNI::CallCharMethodA, + CheckJNI::CallShortMethod, + CheckJNI::CallShortMethodV, + CheckJNI::CallShortMethodA, + CheckJNI::CallIntMethod, + CheckJNI::CallIntMethodV, + CheckJNI::CallIntMethodA, + CheckJNI::CallLongMethod, + CheckJNI::CallLongMethodV, + CheckJNI::CallLongMethodA, + CheckJNI::CallFloatMethod, + CheckJNI::CallFloatMethodV, + CheckJNI::CallFloatMethodA, + CheckJNI::CallDoubleMethod, + CheckJNI::CallDoubleMethodV, + CheckJNI::CallDoubleMethodA, + CheckJNI::CallVoidMethod, + CheckJNI::CallVoidMethodV, + CheckJNI::CallVoidMethodA, + CheckJNI::CallNonvirtualObjectMethod, + CheckJNI::CallNonvirtualObjectMethodV, + CheckJNI::CallNonvirtualObjectMethodA, + CheckJNI::CallNonvirtualBooleanMethod, + CheckJNI::CallNonvirtualBooleanMethodV, + CheckJNI::CallNonvirtualBooleanMethodA, + CheckJNI::CallNonvirtualByteMethod, + CheckJNI::CallNonvirtualByteMethodV, + CheckJNI::CallNonvirtualByteMethodA, + CheckJNI::CallNonvirtualCharMethod, + CheckJNI::CallNonvirtualCharMethodV, + CheckJNI::CallNonvirtualCharMethodA, + CheckJNI::CallNonvirtualShortMethod, + CheckJNI::CallNonvirtualShortMethodV, + CheckJNI::CallNonvirtualShortMethodA, + CheckJNI::CallNonvirtualIntMethod, + CheckJNI::CallNonvirtualIntMethodV, + CheckJNI::CallNonvirtualIntMethodA, + CheckJNI::CallNonvirtualLongMethod, + CheckJNI::CallNonvirtualLongMethodV, + CheckJNI::CallNonvirtualLongMethodA, + CheckJNI::CallNonvirtualFloatMethod, + CheckJNI::CallNonvirtualFloatMethodV, + CheckJNI::CallNonvirtualFloatMethodA, + CheckJNI::CallNonvirtualDoubleMethod, + CheckJNI::CallNonvirtualDoubleMethodV, + CheckJNI::CallNonvirtualDoubleMethodA, + CheckJNI::CallNonvirtualVoidMethod, + CheckJNI::CallNonvirtualVoidMethodV, + CheckJNI::CallNonvirtualVoidMethodA, + CheckJNI::GetFieldID, + CheckJNI::GetObjectField, + CheckJNI::GetBooleanField, + CheckJNI::GetByteField, + CheckJNI::GetCharField, + CheckJNI::GetShortField, + CheckJNI::GetIntField, + CheckJNI::GetLongField, + CheckJNI::GetFloatField, + CheckJNI::GetDoubleField, + CheckJNI::SetObjectField, + CheckJNI::SetBooleanField, + CheckJNI::SetByteField, + CheckJNI::SetCharField, + CheckJNI::SetShortField, + CheckJNI::SetIntField, + CheckJNI::SetLongField, + CheckJNI::SetFloatField, + CheckJNI::SetDoubleField, + CheckJNI::GetStaticMethodID, + CheckJNI::CallStaticObjectMethod, + CheckJNI::CallStaticObjectMethodV, + CheckJNI::CallStaticObjectMethodA, + CheckJNI::CallStaticBooleanMethod, + CheckJNI::CallStaticBooleanMethodV, + CheckJNI::CallStaticBooleanMethodA, + CheckJNI::CallStaticByteMethod, + CheckJNI::CallStaticByteMethodV, + CheckJNI::CallStaticByteMethodA, + CheckJNI::CallStaticCharMethod, + CheckJNI::CallStaticCharMethodV, + CheckJNI::CallStaticCharMethodA, + CheckJNI::CallStaticShortMethod, + CheckJNI::CallStaticShortMethodV, + CheckJNI::CallStaticShortMethodA, + CheckJNI::CallStaticIntMethod, + CheckJNI::CallStaticIntMethodV, + CheckJNI::CallStaticIntMethodA, + CheckJNI::CallStaticLongMethod, + CheckJNI::CallStaticLongMethodV, + CheckJNI::CallStaticLongMethodA, + CheckJNI::CallStaticFloatMethod, + CheckJNI::CallStaticFloatMethodV, + CheckJNI::CallStaticFloatMethodA, + CheckJNI::CallStaticDoubleMethod, + CheckJNI::CallStaticDoubleMethodV, + CheckJNI::CallStaticDoubleMethodA, + CheckJNI::CallStaticVoidMethod, + CheckJNI::CallStaticVoidMethodV, + CheckJNI::CallStaticVoidMethodA, + CheckJNI::GetStaticFieldID, + CheckJNI::GetStaticObjectField, + CheckJNI::GetStaticBooleanField, + CheckJNI::GetStaticByteField, + CheckJNI::GetStaticCharField, + CheckJNI::GetStaticShortField, + CheckJNI::GetStaticIntField, + CheckJNI::GetStaticLongField, + CheckJNI::GetStaticFloatField, + CheckJNI::GetStaticDoubleField, + CheckJNI::SetStaticObjectField, + CheckJNI::SetStaticBooleanField, + CheckJNI::SetStaticByteField, + CheckJNI::SetStaticCharField, + CheckJNI::SetStaticShortField, + CheckJNI::SetStaticIntField, + CheckJNI::SetStaticLongField, + CheckJNI::SetStaticFloatField, + CheckJNI::SetStaticDoubleField, + CheckJNI::NewString, + CheckJNI::GetStringLength, + CheckJNI::GetStringChars, + CheckJNI::ReleaseStringChars, + CheckJNI::NewStringUTF, + CheckJNI::GetStringUTFLength, + CheckJNI::GetStringUTFChars, + CheckJNI::ReleaseStringUTFChars, + CheckJNI::GetArrayLength, + CheckJNI::NewObjectArray, + CheckJNI::GetObjectArrayElement, + CheckJNI::SetObjectArrayElement, + CheckJNI::NewBooleanArray, + CheckJNI::NewByteArray, + CheckJNI::NewCharArray, + CheckJNI::NewShortArray, + CheckJNI::NewIntArray, + CheckJNI::NewLongArray, + CheckJNI::NewFloatArray, + CheckJNI::NewDoubleArray, + CheckJNI::GetBooleanArrayElements, + CheckJNI::GetByteArrayElements, + CheckJNI::GetCharArrayElements, + CheckJNI::GetShortArrayElements, + CheckJNI::GetIntArrayElements, + CheckJNI::GetLongArrayElements, + CheckJNI::GetFloatArrayElements, + CheckJNI::GetDoubleArrayElements, + CheckJNI::ReleaseBooleanArrayElements, + CheckJNI::ReleaseByteArrayElements, + CheckJNI::ReleaseCharArrayElements, + CheckJNI::ReleaseShortArrayElements, + CheckJNI::ReleaseIntArrayElements, + CheckJNI::ReleaseLongArrayElements, + CheckJNI::ReleaseFloatArrayElements, + CheckJNI::ReleaseDoubleArrayElements, + CheckJNI::GetBooleanArrayRegion, + CheckJNI::GetByteArrayRegion, + CheckJNI::GetCharArrayRegion, + CheckJNI::GetShortArrayRegion, + CheckJNI::GetIntArrayRegion, + CheckJNI::GetLongArrayRegion, + CheckJNI::GetFloatArrayRegion, + CheckJNI::GetDoubleArrayRegion, + CheckJNI::SetBooleanArrayRegion, + CheckJNI::SetByteArrayRegion, + CheckJNI::SetCharArrayRegion, + CheckJNI::SetShortArrayRegion, + CheckJNI::SetIntArrayRegion, + CheckJNI::SetLongArrayRegion, + CheckJNI::SetFloatArrayRegion, + CheckJNI::SetDoubleArrayRegion, + CheckJNI::RegisterNatives, + CheckJNI::UnregisterNatives, + CheckJNI::MonitorEnter, + CheckJNI::MonitorExit, + CheckJNI::GetJavaVM, + CheckJNI::GetStringRegion, + CheckJNI::GetStringUTFRegion, + CheckJNI::GetPrimitiveArrayCritical, + CheckJNI::ReleasePrimitiveArrayCritical, + CheckJNI::GetStringCritical, + CheckJNI::ReleaseStringCritical, + CheckJNI::NewWeakGlobalRef, + CheckJNI::DeleteWeakGlobalRef, + CheckJNI::ExceptionCheck, + CheckJNI::NewDirectByteBuffer, + CheckJNI::GetDirectBufferAddress, + CheckJNI::GetDirectBufferCapacity, + CheckJNI::GetObjectRefType, +}; + +const JNINativeInterface* GetCheckJniNativeInterface() { + return &gCheckNativeInterface; +} + +class CheckJII { + public: + static jint DestroyJavaVM(JavaVM* vm) { + ScopedCheck sc(vm, false, __FUNCTION__); + sc.Check(true, "v", vm); + return CHECK_JNI_EXIT("I", BaseVm(vm)->DestroyJavaVM(vm)); + } + + static jint AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args) { + ScopedCheck sc(vm, false, __FUNCTION__); + sc.Check(true, "vpp", vm, p_env, thr_args); + return CHECK_JNI_EXIT("I", BaseVm(vm)->AttachCurrentThread(vm, p_env, thr_args)); + } + + static jint AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** p_env, void* thr_args) { + ScopedCheck sc(vm, false, __FUNCTION__); + sc.Check(true, "vpp", vm, p_env, thr_args); + return CHECK_JNI_EXIT("I", BaseVm(vm)->AttachCurrentThreadAsDaemon(vm, p_env, thr_args)); + } + + static jint DetachCurrentThread(JavaVM* vm) { + ScopedCheck sc(vm, true, __FUNCTION__); + sc.Check(true, "v", vm); + return CHECK_JNI_EXIT("I", BaseVm(vm)->DetachCurrentThread(vm)); + } + + static jint GetEnv(JavaVM* vm, void** env, jint version) { + ScopedCheck sc(vm, true, __FUNCTION__); + sc.Check(true, "vpI", vm); + return CHECK_JNI_EXIT("I", BaseVm(vm)->GetEnv(vm, env, version)); + } + + private: + static inline const JNIInvokeInterface* BaseVm(JavaVM* vm) { + return reinterpret_cast(vm)->unchecked_functions; + } +}; + +const JNIInvokeInterface gCheckInvokeInterface = { + NULL, // reserved0 + NULL, // reserved1 + NULL, // reserved2 + CheckJII::DestroyJavaVM, + CheckJII::AttachCurrentThread, + CheckJII::DetachCurrentThread, + CheckJII::GetEnv, + CheckJII::AttachCurrentThreadAsDaemon +}; + +const JNIInvokeInterface* GetCheckJniInvokeInterface() { + return &gCheckInvokeInterface; +} + +} // namespace art diff --git a/src/class_linker-inl.h b/src/class_linker-inl.h new file mode 100644 index 0000000000000000000000000000000000000000..6cf49912a2d08a9569b3e582fe7d67865aeab2bc --- /dev/null +++ b/src/class_linker-inl.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011 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_SRC_CLASS_LINKER_INL_H_ +#define ART_SRC_CLASS_LINKER_INL_H_ + +#include "class_linker.h" + +#include "mirror/dex_cache.h" +#include "mirror/field.h" +#include "mirror/iftable.h" +#include "mirror/object_array.h" + +namespace art { + +inline mirror::String* ClassLinker::ResolveString(uint32_t string_idx, + const mirror::AbstractMethod* referrer) { + mirror::String* resolved_string = referrer->GetDexCacheStrings()->Get(string_idx); + if (UNLIKELY(resolved_string == NULL)) { + mirror::Class* declaring_class = referrer->GetDeclaringClass(); + mirror::DexCache* dex_cache = declaring_class->GetDexCache(); + const DexFile& dex_file = *dex_cache->GetDexFile(); + resolved_string = ResolveString(dex_file, string_idx, dex_cache); + } + return resolved_string; +} + +inline mirror::Class* ClassLinker::ResolveType(uint16_t type_idx, + const mirror::AbstractMethod* referrer) { + mirror::Class* resolved_type = referrer->GetDexCacheResolvedTypes()->Get(type_idx); + if (UNLIKELY(resolved_type == NULL)) { + mirror::Class* declaring_class = referrer->GetDeclaringClass(); + mirror::DexCache* dex_cache = declaring_class->GetDexCache(); + mirror::ClassLoader* class_loader = declaring_class->GetClassLoader(); + const DexFile& dex_file = *dex_cache->GetDexFile(); + resolved_type = ResolveType(dex_file, type_idx, dex_cache, class_loader); + } + return resolved_type; +} + +inline mirror::Class* ClassLinker::ResolveType(uint16_t type_idx, const mirror::Field* referrer) { + mirror::Class* declaring_class = referrer->GetDeclaringClass(); + mirror::DexCache* dex_cache = declaring_class->GetDexCache(); + mirror::Class* resolved_type = dex_cache->GetResolvedType(type_idx); + if (UNLIKELY(resolved_type == NULL)) { + mirror::ClassLoader* class_loader = declaring_class->GetClassLoader(); + const DexFile& dex_file = *dex_cache->GetDexFile(); + resolved_type = ResolveType(dex_file, type_idx, dex_cache, class_loader); + } + return resolved_type; +} + +inline mirror::AbstractMethod* ClassLinker::ResolveMethod(uint32_t method_idx, + const mirror::AbstractMethod* referrer, + InvokeType type) { + mirror::AbstractMethod* resolved_method = + referrer->GetDexCacheResolvedMethods()->Get(method_idx); + if (UNLIKELY(resolved_method == NULL || resolved_method->IsRuntimeMethod())) { + mirror::Class* declaring_class = referrer->GetDeclaringClass(); + mirror::DexCache* dex_cache = declaring_class->GetDexCache(); + mirror::ClassLoader* class_loader = declaring_class->GetClassLoader(); + const DexFile& dex_file = *dex_cache->GetDexFile(); + resolved_method = ResolveMethod(dex_file, method_idx, dex_cache, class_loader, referrer, type); + } + return resolved_method; +} + +inline mirror::Field* ClassLinker::ResolveField(uint32_t field_idx, + const mirror::AbstractMethod* referrer, + bool is_static) { + mirror::Field* resolved_field = + referrer->GetDeclaringClass()->GetDexCache()->GetResolvedField(field_idx); + if (UNLIKELY(resolved_field == NULL)) { + mirror::Class* declaring_class = referrer->GetDeclaringClass(); + mirror::DexCache* dex_cache = declaring_class->GetDexCache(); + mirror::ClassLoader* class_loader = declaring_class->GetClassLoader(); + const DexFile& dex_file = *dex_cache->GetDexFile(); + resolved_field = ResolveField(dex_file, field_idx, dex_cache, class_loader, is_static); + } + return resolved_field; +} + +template +inline mirror::ObjectArray* ClassLinker::AllocObjectArray(Thread* self, size_t length) { + return mirror::ObjectArray::Alloc(self, GetClassRoot(kObjectArrayClass), length); +} + +inline mirror::ObjectArray* ClassLinker::AllocClassArray(Thread* self, + size_t length) { + return mirror::ObjectArray::Alloc(self, GetClassRoot(kClassArrayClass), length); +} + +inline mirror::ObjectArray* ClassLinker::AllocStringArray(Thread* self, + size_t length) { + return mirror::ObjectArray::Alloc(self, GetClassRoot(kJavaLangStringArrayClass), + length); +} + +inline mirror::ObjectArray* ClassLinker::AllocAbstractMethodArray(Thread* self, + size_t length) { + return mirror::ObjectArray::Alloc(self, + GetClassRoot(kJavaLangReflectAbstractMethodArrayClass), length); +} + +inline mirror::ObjectArray* ClassLinker::AllocMethodArray(Thread* self, + size_t length) { + return mirror::ObjectArray::Alloc(self, + GetClassRoot(kJavaLangReflectMethodArrayClass), length); +} + +inline mirror::IfTable* ClassLinker::AllocIfTable(Thread* self, size_t ifcount) { + return down_cast( + mirror::IfTable::Alloc(self, GetClassRoot(kObjectArrayClass), ifcount * mirror::IfTable::kMax)); +} + +inline mirror::ObjectArray* ClassLinker::AllocFieldArray(Thread* self, + size_t length) { + return mirror::ObjectArray::Alloc(self, + GetClassRoot(kJavaLangReflectFieldArrayClass), + length); +} + +inline mirror::Class* ClassLinker::GetClassRoot(ClassRoot class_root) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + DCHECK(class_roots_ != NULL); + mirror::Class* klass = class_roots_->Get(class_root); + DCHECK(klass != NULL); + return klass; +} + +} // namespace art + +#endif // ART_SRC_CLASS_LINKER_INL_H_ diff --git a/src/class_linker.cc b/src/class_linker.cc new file mode 100644 index 0000000000000000000000000000000000000000..52ce964ec846fa46a8c0df426ec29d6c47a70ea1 --- /dev/null +++ b/src/class_linker.cc @@ -0,0 +1,3927 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "class_linker.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "base/casts.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/unix_file/fd_file.h" +#include "class_linker-inl.h" +#include "debugger.h" +#include "dex_file-inl.h" +#include "gc/card_table-inl.h" +#include "heap.h" +#include "intern_table.h" +#include "interpreter/interpreter.h" +#include "leb128.h" +#include "oat.h" +#include "oat_file.h" +#include "mirror/class.h" +#include "mirror/class-inl.h" +#include "mirror/class_loader.h" +#include "mirror/dex_cache.h" +#include "mirror/field-inl.h" +#include "mirror/iftable-inl.h" +#include "mirror/abstract_method.h" +#include "mirror/abstract_method-inl.h" +#include "mirror/object-inl.h" +#include "mirror/object_array-inl.h" +#include "mirror/proxy.h" +#include "mirror/stack_trace_element.h" +#include "object_utils.h" +#include "os.h" +#include "runtime.h" +#include "runtime_support.h" +#if defined(ART_USE_PORTABLE_COMPILER) +#include "compiler/llvm/runtime_support_llvm.h" +#endif +#include "ScopedLocalRef.h" +#include "scoped_thread_state_change.h" +#include "sirt_ref.h" +#include "gc/space.h" +#include "gc/space_bitmap.h" +#include "stack_indirect_reference_table.h" +#include "thread.h" +#include "UniquePtr.h" +#include "utils.h" +#include "verifier/method_verifier.h" +#include "well_known_classes.h" + +namespace art { + +void artInterpreterToQuickEntry(Thread* self, ShadowFrame* shadow_frame, JValue* result); + +static void ThrowNoClassDefFoundError(const char* fmt, ...) + __attribute__((__format__(__printf__, 1, 2))) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); +static void ThrowNoClassDefFoundError(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + Thread* self = Thread::Current(); + ThrowLocation throw_location = self->GetCurrentLocationForThrow(); + self->ThrowNewExceptionV(throw_location, "Ljava/lang/NoClassDefFoundError;", fmt, args); + va_end(args); +} + +static void ThrowEarlierClassFailure(mirror::Class* c) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + // The class failed to initialize on a previous attempt, so we want to throw + // a NoClassDefFoundError (v2 2.17.5). The exception to this rule is if we + // failed in verification, in which case v2 5.4.1 says we need to re-throw + // the previous error. + if (!Runtime::Current()->IsCompiler()) { // Give info if this occurs at runtime. + LOG(INFO) << "Rejecting re-init on previously-failed class " << PrettyClass(c); + } + + CHECK(c->IsErroneous()) << PrettyClass(c) << " " << c->GetStatus(); + Thread* self = Thread::Current(); + ThrowLocation throw_location = self->GetCurrentLocationForThrow(); + if (c->GetVerifyErrorClass() != NULL) { + // TODO: change the verifier to store an _instance_, with a useful detail message? + ClassHelper ve_ch(c->GetVerifyErrorClass()); + self->ThrowNewException(throw_location, ve_ch.GetDescriptor(), PrettyDescriptor(c).c_str()); + } else { + self->ThrowNewException(throw_location, "Ljava/lang/NoClassDefFoundError;", + PrettyDescriptor(c).c_str()); + } +} + +static void WrapExceptionInInitializer() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + Thread* self = Thread::Current(); + JNIEnv* env = self->GetJniEnv(); + + ScopedLocalRef cause(env, env->ExceptionOccurred()); + CHECK(cause.get() != NULL); + + env->ExceptionClear(); + bool is_error = env->IsInstanceOf(cause.get(), WellKnownClasses::java_lang_Error); + env->Throw(cause.get()); + + // We only wrap non-Error exceptions; an Error can just be used as-is. + if (!is_error) { + ThrowLocation throw_location = self->GetCurrentLocationForThrow(); + self->ThrowNewWrappedException(throw_location, "Ljava/lang/ExceptionInInitializerError;", NULL); + } +} + +static size_t Hash(const char* s) { + // This is the java.lang.String hashcode for convenience, not interoperability. + size_t hash = 0; + for (; *s != '\0'; ++s) { + hash = hash * 31 + *s; + } + return hash; +} + +const char* ClassLinker::class_roots_descriptors_[] = { + "Ljava/lang/Class;", + "Ljava/lang/Object;", + "[Ljava/lang/Class;", + "[Ljava/lang/Object;", + "Ljava/lang/String;", + "Ljava/lang/DexCache;", + "Ljava/lang/ref/Reference;", + "Ljava/lang/reflect/Constructor;", + "Ljava/lang/reflect/Field;", + "Ljava/lang/reflect/AbstractMethod;", + "Ljava/lang/reflect/Method;", + "Ljava/lang/reflect/Proxy;", + "[Ljava/lang/String;", + "[Ljava/lang/reflect/AbstractMethod;", + "[Ljava/lang/reflect/Field;", + "[Ljava/lang/reflect/Method;", + "Ljava/lang/ClassLoader;", + "Ljava/lang/Throwable;", + "Ljava/lang/ClassNotFoundException;", + "Ljava/lang/StackTraceElement;", + "Z", + "B", + "C", + "D", + "F", + "I", + "J", + "S", + "V", + "[Z", + "[B", + "[C", + "[D", + "[F", + "[I", + "[J", + "[S", + "[Ljava/lang/StackTraceElement;", +}; + +ClassLinker* ClassLinker::CreateFromCompiler(const std::vector& boot_class_path, + InternTable* intern_table) { + CHECK_NE(boot_class_path.size(), 0U); + UniquePtr class_linker(new ClassLinker(intern_table)); + class_linker->InitFromCompiler(boot_class_path); + return class_linker.release(); +} + +ClassLinker* ClassLinker::CreateFromImage(InternTable* intern_table) { + UniquePtr class_linker(new ClassLinker(intern_table)); + class_linker->InitFromImage(); + return class_linker.release(); +} + +ClassLinker::ClassLinker(InternTable* intern_table) + // dex_lock_ is recursive as it may be used in stack dumping. + : dex_lock_("ClassLinker dex lock", kDefaultMutexLevel, true), + class_roots_(NULL), + array_iftable_(NULL), + init_done_(false), + is_dirty_(false), + intern_table_(intern_table) { + CHECK_EQ(arraysize(class_roots_descriptors_), size_t(kClassRootsMax)); +} + +void ClassLinker::InitFromCompiler(const std::vector& boot_class_path) { + VLOG(startup) << "ClassLinker::Init"; + CHECK(Runtime::Current()->IsCompiler()); + + CHECK(!init_done_); + + // java_lang_Class comes first, it's needed for AllocClass + Thread* self = Thread::Current(); + Heap* heap = Runtime::Current()->GetHeap(); + SirtRef + java_lang_Class(self, + down_cast(heap->AllocObject(self, NULL, + sizeof(mirror::ClassClass)))); + CHECK(java_lang_Class.get() != NULL); + mirror::Class::SetClassClass(java_lang_Class.get()); + java_lang_Class->SetClass(java_lang_Class.get()); + java_lang_Class->SetClassSize(sizeof(mirror::ClassClass)); + // AllocClass(mirror::Class*) can now be used + + // Class[] is used for reflection support. + SirtRef class_array_class(self, AllocClass(self, java_lang_Class.get(), sizeof(mirror::Class))); + class_array_class->SetComponentType(java_lang_Class.get()); + + // java_lang_Object comes next so that object_array_class can be created. + SirtRef java_lang_Object(self, AllocClass(self, java_lang_Class.get(), sizeof(mirror::Class))); + CHECK(java_lang_Object.get() != NULL); + // backfill Object as the super class of Class. + java_lang_Class->SetSuperClass(java_lang_Object.get()); + java_lang_Object->SetStatus(mirror::Class::kStatusLoaded); + + // Object[] next to hold class roots. + SirtRef object_array_class(self, AllocClass(self, java_lang_Class.get(), sizeof(mirror::Class))); + object_array_class->SetComponentType(java_lang_Object.get()); + + // Setup the char class to be used for char[]. + SirtRef char_class(self, AllocClass(self, java_lang_Class.get(), sizeof(mirror::Class))); + + // Setup the char[] class to be used for String. + SirtRef char_array_class(self, AllocClass(self, java_lang_Class.get(), sizeof(mirror::Class))); + char_array_class->SetComponentType(char_class.get()); + mirror::CharArray::SetArrayClass(char_array_class.get()); + + // Setup String. + SirtRef java_lang_String(self, AllocClass(self, java_lang_Class.get(), sizeof(mirror::StringClass))); + mirror::String::SetClass(java_lang_String.get()); + java_lang_String->SetObjectSize(sizeof(mirror::String)); + java_lang_String->SetStatus(mirror::Class::kStatusResolved); + + // Create storage for root classes, save away our work so far (requires descriptors). + class_roots_ = mirror::ObjectArray::Alloc(self, object_array_class.get(), kClassRootsMax); + CHECK(class_roots_ != NULL); + SetClassRoot(kJavaLangClass, java_lang_Class.get()); + SetClassRoot(kJavaLangObject, java_lang_Object.get()); + SetClassRoot(kClassArrayClass, class_array_class.get()); + SetClassRoot(kObjectArrayClass, object_array_class.get()); + SetClassRoot(kCharArrayClass, char_array_class.get()); + SetClassRoot(kJavaLangString, java_lang_String.get()); + + // Setup the primitive type classes. + SetClassRoot(kPrimitiveBoolean, CreatePrimitiveClass(self, Primitive::kPrimBoolean)); + SetClassRoot(kPrimitiveByte, CreatePrimitiveClass(self, Primitive::kPrimByte)); + SetClassRoot(kPrimitiveShort, CreatePrimitiveClass(self, Primitive::kPrimShort)); + SetClassRoot(kPrimitiveInt, CreatePrimitiveClass(self, Primitive::kPrimInt)); + SetClassRoot(kPrimitiveLong, CreatePrimitiveClass(self, Primitive::kPrimLong)); + SetClassRoot(kPrimitiveFloat, CreatePrimitiveClass(self, Primitive::kPrimFloat)); + SetClassRoot(kPrimitiveDouble, CreatePrimitiveClass(self, Primitive::kPrimDouble)); + SetClassRoot(kPrimitiveVoid, CreatePrimitiveClass(self, Primitive::kPrimVoid)); + + // Create array interface entries to populate once we can load system classes. + array_iftable_ = AllocIfTable(self, 2); + + // Create int array type for AllocDexCache (done in AppendToBootClassPath). + SirtRef int_array_class(self, AllocClass(self, java_lang_Class.get(), sizeof(mirror::Class))); + int_array_class->SetComponentType(GetClassRoot(kPrimitiveInt)); + mirror::IntArray::SetArrayClass(int_array_class.get()); + SetClassRoot(kIntArrayClass, int_array_class.get()); + + // now that these are registered, we can use AllocClass() and AllocObjectArray + + // Set up DexCache. This cannot be done later since AppendToBootClassPath calls AllocDexCache. + SirtRef + java_lang_DexCache(self, AllocClass(self, java_lang_Class.get(), sizeof(mirror::DexCacheClass))); + SetClassRoot(kJavaLangDexCache, java_lang_DexCache.get()); + java_lang_DexCache->SetObjectSize(sizeof(mirror::DexCacheClass)); + java_lang_DexCache->SetStatus(mirror::Class::kStatusResolved); + + // Constructor, Field, Method, and AbstractMethod are necessary so that FindClass can link members. + SirtRef java_lang_reflect_Field(self, AllocClass(self, java_lang_Class.get(), + sizeof(mirror::FieldClass))); + CHECK(java_lang_reflect_Field.get() != NULL); + java_lang_reflect_Field->SetObjectSize(sizeof(mirror::Field)); + SetClassRoot(kJavaLangReflectField, java_lang_reflect_Field.get()); + java_lang_reflect_Field->SetStatus(mirror::Class::kStatusResolved); + mirror::Field::SetClass(java_lang_reflect_Field.get()); + + SirtRef java_lang_reflect_AbstractMethod(self, AllocClass(self, java_lang_Class.get(), + sizeof(mirror::AbstractMethodClass))); + CHECK(java_lang_reflect_AbstractMethod.get() != NULL); + java_lang_reflect_AbstractMethod->SetObjectSize(sizeof(mirror::AbstractMethod)); + SetClassRoot(kJavaLangReflectAbstractMethod, java_lang_reflect_AbstractMethod.get()); + java_lang_reflect_AbstractMethod->SetStatus(mirror::Class::kStatusResolved); + + SirtRef java_lang_reflect_Constructor(self, AllocClass(self, java_lang_Class.get(), + sizeof(mirror::AbstractMethodClass))); + CHECK(java_lang_reflect_Constructor.get() != NULL); + java_lang_reflect_Constructor->SetObjectSize(sizeof(mirror::Constructor)); + java_lang_reflect_Constructor->SetSuperClass(java_lang_reflect_AbstractMethod.get()); + SetClassRoot(kJavaLangReflectConstructor, java_lang_reflect_Constructor.get()); + java_lang_reflect_Constructor->SetStatus(mirror::Class::kStatusResolved); + + SirtRef java_lang_reflect_Method(self, AllocClass(self, java_lang_Class.get(), + sizeof(mirror::AbstractMethodClass))); + CHECK(java_lang_reflect_Method.get() != NULL); + java_lang_reflect_Method->SetObjectSize(sizeof(mirror::Method)); + java_lang_reflect_Method->SetSuperClass(java_lang_reflect_AbstractMethod.get()); + SetClassRoot(kJavaLangReflectMethod, java_lang_reflect_Method.get()); + java_lang_reflect_Method->SetStatus(mirror::Class::kStatusResolved); + + mirror::AbstractMethod::SetClasses(java_lang_reflect_Constructor.get(), + java_lang_reflect_Method.get()); + + // Set up array classes for string, field, method + SirtRef object_array_string(self, AllocClass(self, java_lang_Class.get(), sizeof(mirror::Class))); + object_array_string->SetComponentType(java_lang_String.get()); + SetClassRoot(kJavaLangStringArrayClass, object_array_string.get()); + + SirtRef object_array_abstract_method(self, AllocClass(self, java_lang_Class.get(), sizeof(mirror::Class))); + object_array_abstract_method->SetComponentType(java_lang_reflect_AbstractMethod.get()); + SetClassRoot(kJavaLangReflectAbstractMethodArrayClass, object_array_abstract_method.get()); + + SirtRef object_array_field(self, AllocClass(self, java_lang_Class.get(), sizeof(mirror::Class))); + object_array_field->SetComponentType(java_lang_reflect_Field.get()); + SetClassRoot(kJavaLangReflectFieldArrayClass, object_array_field.get()); + + SirtRef object_array_method(self, AllocClass(self, java_lang_Class.get(), sizeof(mirror::Class))); + object_array_method->SetComponentType(java_lang_reflect_Method.get()); + SetClassRoot(kJavaLangReflectMethodArrayClass, object_array_method.get()); + + // Setup boot_class_path_ and register class_path now that we can use AllocObjectArray to create + // DexCache instances. Needs to be after String, Field, Method arrays since AllocDexCache uses + // these roots. + CHECK_NE(0U, boot_class_path.size()); + for (size_t i = 0; i != boot_class_path.size(); ++i) { + const DexFile* dex_file = boot_class_path[i]; + CHECK(dex_file != NULL); + AppendToBootClassPath(*dex_file); + } + + // now we can use FindSystemClass + + // run char class through InitializePrimitiveClass to finish init + InitializePrimitiveClass(char_class.get(), Primitive::kPrimChar); + SetClassRoot(kPrimitiveChar, char_class.get()); // needs descriptor + + // Object, String and DexCache need to be rerun through FindSystemClass to finish init + java_lang_Object->SetStatus(mirror::Class::kStatusNotReady); + mirror::Class* Object_class = FindSystemClass("Ljava/lang/Object;"); + CHECK_EQ(java_lang_Object.get(), Object_class); + CHECK_EQ(java_lang_Object->GetObjectSize(), sizeof(mirror::Object)); + java_lang_String->SetStatus(mirror::Class::kStatusNotReady); + mirror::Class* String_class = FindSystemClass("Ljava/lang/String;"); + CHECK_EQ(java_lang_String.get(), String_class); + CHECK_EQ(java_lang_String->GetObjectSize(), sizeof(mirror::String)); + java_lang_DexCache->SetStatus(mirror::Class::kStatusNotReady); + mirror::Class* DexCache_class = FindSystemClass("Ljava/lang/DexCache;"); + CHECK_EQ(java_lang_String.get(), String_class); + CHECK_EQ(java_lang_DexCache.get(), DexCache_class); + CHECK_EQ(java_lang_DexCache->GetObjectSize(), sizeof(mirror::DexCache)); + + // Setup the primitive array type classes - can't be done until Object has a vtable. + SetClassRoot(kBooleanArrayClass, FindSystemClass("[Z")); + mirror::BooleanArray::SetArrayClass(GetClassRoot(kBooleanArrayClass)); + + SetClassRoot(kByteArrayClass, FindSystemClass("[B")); + mirror::ByteArray::SetArrayClass(GetClassRoot(kByteArrayClass)); + + mirror::Class* found_char_array_class = FindSystemClass("[C"); + CHECK_EQ(char_array_class.get(), found_char_array_class); + + SetClassRoot(kShortArrayClass, FindSystemClass("[S")); + mirror::ShortArray::SetArrayClass(GetClassRoot(kShortArrayClass)); + + mirror::Class* found_int_array_class = FindSystemClass("[I"); + CHECK_EQ(int_array_class.get(), found_int_array_class); + + SetClassRoot(kLongArrayClass, FindSystemClass("[J")); + mirror::LongArray::SetArrayClass(GetClassRoot(kLongArrayClass)); + + SetClassRoot(kFloatArrayClass, FindSystemClass("[F")); + mirror::FloatArray::SetArrayClass(GetClassRoot(kFloatArrayClass)); + + SetClassRoot(kDoubleArrayClass, FindSystemClass("[D")); + mirror::DoubleArray::SetArrayClass(GetClassRoot(kDoubleArrayClass)); + + mirror::Class* found_class_array_class = FindSystemClass("[Ljava/lang/Class;"); + CHECK_EQ(class_array_class.get(), found_class_array_class); + + mirror::Class* found_object_array_class = FindSystemClass("[Ljava/lang/Object;"); + CHECK_EQ(object_array_class.get(), found_object_array_class); + + // Setup the single, global copy of "iftable". + mirror::Class* java_lang_Cloneable = FindSystemClass("Ljava/lang/Cloneable;"); + CHECK(java_lang_Cloneable != NULL); + mirror::Class* java_io_Serializable = FindSystemClass("Ljava/io/Serializable;"); + CHECK(java_io_Serializable != NULL); + // We assume that Cloneable/Serializable don't have superinterfaces -- normally we'd have to + // crawl up and explicitly list all of the supers as well. + array_iftable_->SetInterface(0, java_lang_Cloneable); + array_iftable_->SetInterface(1, java_io_Serializable); + + // Sanity check Class[] and Object[]'s interfaces. + ClassHelper kh(class_array_class.get(), this); + CHECK_EQ(java_lang_Cloneable, kh.GetDirectInterface(0)); + CHECK_EQ(java_io_Serializable, kh.GetDirectInterface(1)); + kh.ChangeClass(object_array_class.get()); + CHECK_EQ(java_lang_Cloneable, kh.GetDirectInterface(0)); + CHECK_EQ(java_io_Serializable, kh.GetDirectInterface(1)); + // Run Class, Constructor, Field, and Method through FindSystemClass. This initializes their + // dex_cache_ fields and register them in classes_. + mirror::Class* Class_class = FindSystemClass("Ljava/lang/Class;"); + CHECK_EQ(java_lang_Class.get(), Class_class); + + java_lang_reflect_AbstractMethod->SetStatus(mirror::Class::kStatusNotReady); + mirror::Class* Abstract_method_class = FindSystemClass("Ljava/lang/reflect/AbstractMethod;"); + CHECK_EQ(java_lang_reflect_AbstractMethod.get(), Abstract_method_class); + + // Method extends AbstractMethod so must reset after. + java_lang_reflect_Method->SetStatus(mirror::Class::kStatusNotReady); + mirror::Class* Method_class = FindSystemClass("Ljava/lang/reflect/Method;"); + CHECK_EQ(java_lang_reflect_Method.get(), Method_class); + + // Constructor extends AbstractMethod so must reset after. + java_lang_reflect_Constructor->SetStatus(mirror::Class::kStatusNotReady); + mirror::Class* Constructor_class = FindSystemClass("Ljava/lang/reflect/Constructor;"); + CHECK_EQ(java_lang_reflect_Constructor.get(), Constructor_class); + + java_lang_reflect_Field->SetStatus(mirror::Class::kStatusNotReady); + mirror::Class* Field_class = FindSystemClass("Ljava/lang/reflect/Field;"); + CHECK_EQ(java_lang_reflect_Field.get(), Field_class); + + mirror::Class* String_array_class = FindSystemClass(class_roots_descriptors_[kJavaLangStringArrayClass]); + CHECK_EQ(object_array_string.get(), String_array_class); + + mirror::Class* Abstract_method_array_class = + FindSystemClass(class_roots_descriptors_[kJavaLangReflectAbstractMethodArrayClass]); + CHECK_EQ(object_array_abstract_method.get(), Abstract_method_array_class); + + mirror::Class* Field_array_class = FindSystemClass(class_roots_descriptors_[kJavaLangReflectFieldArrayClass]); + CHECK_EQ(object_array_field.get(), Field_array_class); + + mirror::Class* Method_array_class = + FindSystemClass(class_roots_descriptors_[kJavaLangReflectMethodArrayClass]); + CHECK_EQ(object_array_method.get(), Method_array_class); + + // End of special init trickery, subsequent classes may be loaded via FindSystemClass. + + // Create java.lang.reflect.Proxy root. + mirror::Class* java_lang_reflect_Proxy = FindSystemClass("Ljava/lang/reflect/Proxy;"); + SetClassRoot(kJavaLangReflectProxy, java_lang_reflect_Proxy); + + // java.lang.ref classes need to be specially flagged, but otherwise are normal classes + mirror::Class* java_lang_ref_Reference = FindSystemClass("Ljava/lang/ref/Reference;"); + SetClassRoot(kJavaLangRefReference, java_lang_ref_Reference); + mirror::Class* java_lang_ref_FinalizerReference = FindSystemClass("Ljava/lang/ref/FinalizerReference;"); + java_lang_ref_FinalizerReference->SetAccessFlags( + java_lang_ref_FinalizerReference->GetAccessFlags() | + kAccClassIsReference | kAccClassIsFinalizerReference); + mirror::Class* java_lang_ref_PhantomReference = FindSystemClass("Ljava/lang/ref/PhantomReference;"); + java_lang_ref_PhantomReference->SetAccessFlags( + java_lang_ref_PhantomReference->GetAccessFlags() | + kAccClassIsReference | kAccClassIsPhantomReference); + mirror::Class* java_lang_ref_SoftReference = FindSystemClass("Ljava/lang/ref/SoftReference;"); + java_lang_ref_SoftReference->SetAccessFlags( + java_lang_ref_SoftReference->GetAccessFlags() | kAccClassIsReference); + mirror::Class* java_lang_ref_WeakReference = FindSystemClass("Ljava/lang/ref/WeakReference;"); + java_lang_ref_WeakReference->SetAccessFlags( + java_lang_ref_WeakReference->GetAccessFlags() | + kAccClassIsReference | kAccClassIsWeakReference); + + // Setup the ClassLoader, verifying the object_size_. + mirror::Class* java_lang_ClassLoader = FindSystemClass("Ljava/lang/ClassLoader;"); + CHECK_EQ(java_lang_ClassLoader->GetObjectSize(), sizeof(mirror::ClassLoader)); + SetClassRoot(kJavaLangClassLoader, java_lang_ClassLoader); + + // Set up java.lang.Throwable, java.lang.ClassNotFoundException, and + // java.lang.StackTraceElement as a convenience. + SetClassRoot(kJavaLangThrowable, FindSystemClass("Ljava/lang/Throwable;")); + mirror::Throwable::SetClass(GetClassRoot(kJavaLangThrowable)); + SetClassRoot(kJavaLangClassNotFoundException, FindSystemClass("Ljava/lang/ClassNotFoundException;")); + SetClassRoot(kJavaLangStackTraceElement, FindSystemClass("Ljava/lang/StackTraceElement;")); + SetClassRoot(kJavaLangStackTraceElementArrayClass, FindSystemClass("[Ljava/lang/StackTraceElement;")); + mirror::StackTraceElement::SetClass(GetClassRoot(kJavaLangStackTraceElement)); + + FinishInit(); + + VLOG(startup) << "ClassLinker::InitFromCompiler exiting"; +} + +void ClassLinker::FinishInit() { + VLOG(startup) << "ClassLinker::FinishInit entering"; + + // Let the heap know some key offsets into java.lang.ref instances + // Note: we hard code the field indexes here rather than using FindInstanceField + // as the types of the field can't be resolved prior to the runtime being + // fully initialized + mirror::Class* java_lang_ref_Reference = GetClassRoot(kJavaLangRefReference); + mirror::Class* java_lang_ref_ReferenceQueue = FindSystemClass("Ljava/lang/ref/ReferenceQueue;"); + mirror::Class* java_lang_ref_FinalizerReference = FindSystemClass("Ljava/lang/ref/FinalizerReference;"); + + const DexFile& java_lang_dex = *java_lang_ref_Reference->GetDexCache()->GetDexFile(); + + mirror::Field* pendingNext = java_lang_ref_Reference->GetInstanceField(0); + FieldHelper fh(pendingNext, this); + CHECK_STREQ(fh.GetName(), "pendingNext"); + CHECK_EQ(java_lang_dex.GetFieldId(pendingNext->GetDexFieldIndex()).type_idx_, + java_lang_ref_Reference->GetDexTypeIndex()); + + mirror::Field* queue = java_lang_ref_Reference->GetInstanceField(1); + fh.ChangeField(queue); + CHECK_STREQ(fh.GetName(), "queue"); + CHECK_EQ(java_lang_dex.GetFieldId(queue->GetDexFieldIndex()).type_idx_, + java_lang_ref_ReferenceQueue->GetDexTypeIndex()); + + mirror::Field* queueNext = java_lang_ref_Reference->GetInstanceField(2); + fh.ChangeField(queueNext); + CHECK_STREQ(fh.GetName(), "queueNext"); + CHECK_EQ(java_lang_dex.GetFieldId(queueNext->GetDexFieldIndex()).type_idx_, + java_lang_ref_Reference->GetDexTypeIndex()); + + mirror::Field* referent = java_lang_ref_Reference->GetInstanceField(3); + fh.ChangeField(referent); + CHECK_STREQ(fh.GetName(), "referent"); + CHECK_EQ(java_lang_dex.GetFieldId(referent->GetDexFieldIndex()).type_idx_, + GetClassRoot(kJavaLangObject)->GetDexTypeIndex()); + + mirror::Field* zombie = java_lang_ref_FinalizerReference->GetInstanceField(2); + fh.ChangeField(zombie); + CHECK_STREQ(fh.GetName(), "zombie"); + CHECK_EQ(java_lang_dex.GetFieldId(zombie->GetDexFieldIndex()).type_idx_, + GetClassRoot(kJavaLangObject)->GetDexTypeIndex()); + + Heap* heap = Runtime::Current()->GetHeap(); + heap->SetReferenceOffsets(referent->GetOffset(), + queue->GetOffset(), + queueNext->GetOffset(), + pendingNext->GetOffset(), + zombie->GetOffset()); + + // ensure all class_roots_ are initialized + for (size_t i = 0; i < kClassRootsMax; i++) { + ClassRoot class_root = static_cast(i); + mirror::Class* klass = GetClassRoot(class_root); + CHECK(klass != NULL); + DCHECK(klass->IsArrayClass() || klass->IsPrimitive() || klass->GetDexCache() != NULL); + // note SetClassRoot does additional validation. + // if possible add new checks there to catch errors early + } + + CHECK(array_iftable_ != NULL); + + // disable the slow paths in FindClass and CreatePrimitiveClass now + // that Object, Class, and Object[] are setup + init_done_ = true; + + VLOG(startup) << "ClassLinker::FinishInit exiting"; +} + +void ClassLinker::RunRootClinits() { + Thread* self = Thread::Current(); + for (size_t i = 0; i < ClassLinker::kClassRootsMax; ++i) { + mirror::Class* c = GetClassRoot(ClassRoot(i)); + if (!c->IsArrayClass() && !c->IsPrimitive()) { + EnsureInitialized(GetClassRoot(ClassRoot(i)), true, true); + self->AssertNoPendingException(); + } + } +} + +bool ClassLinker::GenerateOatFile(const std::string& dex_filename, + int oat_fd, + const std::string& oat_cache_filename) { + std::string dex2oat_string(GetAndroidRoot()); + dex2oat_string += (kIsDebugBuild ? "/bin/dex2oatd" : "/bin/dex2oat"); + const char* dex2oat = dex2oat_string.c_str(); + + const char* class_path = Runtime::Current()->GetClassPathString().c_str(); + + Heap* heap = Runtime::Current()->GetHeap(); + std::string boot_image_option_string("--boot-image="); + boot_image_option_string += heap->GetImageSpace()->GetImageFilename(); + const char* boot_image_option = boot_image_option_string.c_str(); + + std::string dex_file_option_string("--dex-file="); + dex_file_option_string += dex_filename; + const char* dex_file_option = dex_file_option_string.c_str(); + + std::string oat_fd_option_string("--oat-fd="); + StringAppendF(&oat_fd_option_string, "%d", oat_fd); + const char* oat_fd_option = oat_fd_option_string.c_str(); + + std::string oat_location_option_string("--oat-location="); + oat_location_option_string += oat_cache_filename; + const char* oat_location_option = oat_location_option_string.c_str(); + + // fork and exec dex2oat + pid_t pid = fork(); + if (pid == 0) { + // no allocation allowed between fork and exec + + // change process groups, so we don't get reaped by ProcessManager + setpgid(0, 0); + + VLOG(class_linker) << dex2oat + << " --runtime-arg -Xms64m" + << " --runtime-arg -Xmx64m" + << " --runtime-arg -classpath" + << " --runtime-arg " << class_path +#if !defined(ART_TARGET) + << " --host" +#endif + << " " << boot_image_option + << " " << dex_file_option + << " " << oat_fd_option + << " " << oat_location_option; + + execl(dex2oat, dex2oat, + "--runtime-arg", "-Xms64m", + "--runtime-arg", "-Xmx64m", + "--runtime-arg", "-classpath", + "--runtime-arg", class_path, +#if !defined(ART_TARGET) + "--host", +#endif + boot_image_option, + dex_file_option, + oat_fd_option, + oat_location_option, + NULL); + + PLOG(FATAL) << "execl(" << dex2oat << ") failed"; + return false; + } else { + // wait for dex2oat to finish + int status; + pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0)); + if (got_pid != pid) { + PLOG(ERROR) << "waitpid failed: wanted " << pid << ", got " << got_pid; + return false; + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + LOG(ERROR) << dex2oat << " failed with dex-file=" << dex_filename; + return false; + } + } + return true; +} + +void ClassLinker::RegisterOatFile(const OatFile& oat_file) { + MutexLock mu(Thread::Current(), dex_lock_); + RegisterOatFileLocked(oat_file); +} + +void ClassLinker::RegisterOatFileLocked(const OatFile& oat_file) { + dex_lock_.AssertHeld(Thread::Current()); +#ifndef NDEBUG + for (size_t i = 0; i < oat_files_.size(); ++i) { + CHECK_NE(&oat_file, oat_files_[i]) << oat_file.GetLocation(); + } +#endif + oat_files_.push_back(&oat_file); +} + +OatFile* ClassLinker::OpenOat(const ImageSpace* space) { + MutexLock mu(Thread::Current(), dex_lock_); + const Runtime* runtime = Runtime::Current(); + const ImageHeader& image_header = space->GetImageHeader(); + // Grab location but don't use Object::AsString as we haven't yet initialized the roots to + // check the down cast + mirror::String* oat_location = + down_cast(image_header.GetImageRoot(ImageHeader::kOatLocation)); + std::string oat_filename; + oat_filename += runtime->GetHostPrefix(); + oat_filename += oat_location->ToModifiedUtf8(); + runtime->GetHeap()->UnReserveOatFileAddressRange(); + OatFile* oat_file = OatFile::Open(oat_filename, oat_filename, image_header.GetOatDataBegin()); + VLOG(startup) << "ClassLinker::OpenOat entering oat_filename=" << oat_filename; + if (oat_file == NULL) { + LOG(ERROR) << "Failed to open oat file " << oat_filename << " referenced from image."; + return NULL; + } + uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum(); + uint32_t image_oat_checksum = image_header.GetOatChecksum(); + if (oat_checksum != image_oat_checksum) { + LOG(ERROR) << "Failed to match oat file checksum " << std::hex << oat_checksum + << " to expected oat checksum " << std::hex << image_oat_checksum + << " in image"; + return NULL; + } + RegisterOatFileLocked(*oat_file); + VLOG(startup) << "ClassLinker::OpenOat exiting"; + return oat_file; +} + +const OatFile* ClassLinker::FindOpenedOatFileForDexFile(const DexFile& dex_file) { + MutexLock mu(Thread::Current(), dex_lock_); + return FindOpenedOatFileFromDexLocation(dex_file.GetLocation()); +} + +const OatFile* ClassLinker::FindOpenedOatFileFromDexLocation(const std::string& dex_location) { + for (size_t i = 0; i < oat_files_.size(); i++) { + const OatFile* oat_file = oat_files_[i]; + DCHECK(oat_file != NULL); + const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location, false); + if (oat_dex_file != NULL) { + return oat_file; + } + } + return NULL; +} + +static const DexFile* FindDexFileInOatLocation(const std::string& dex_location, + uint32_t dex_location_checksum, + const std::string& oat_location) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + UniquePtr oat_file(OatFile::Open(oat_location, oat_location, NULL)); + if (oat_file.get() == NULL) { + return NULL; + } + Runtime* runtime = Runtime::Current(); + const ImageHeader& image_header = runtime->GetHeap()->GetImageSpace()->GetImageHeader(); + if (oat_file->GetOatHeader().GetImageFileLocationOatChecksum() != image_header.GetOatChecksum()) { + return NULL; + } + if (oat_file->GetOatHeader().GetImageFileLocationOatDataBegin() + != reinterpret_cast(image_header.GetOatDataBegin())) { + return NULL; + } + const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location); + if (oat_dex_file == NULL) { + return NULL; + } + if (oat_dex_file->GetDexFileLocationChecksum() != dex_location_checksum) { + return NULL; + } + runtime->GetClassLinker()->RegisterOatFile(*oat_file.release()); + return oat_dex_file->OpenDexFile(); +} + +const DexFile* ClassLinker::FindOrCreateOatFileForDexLocation(const std::string& dex_location, + const std::string& oat_location) { + MutexLock mu(Thread::Current(), dex_lock_); + return FindOrCreateOatFileForDexLocationLocked(dex_location, oat_location); +} + +const DexFile* ClassLinker::FindOrCreateOatFileForDexLocationLocked(const std::string& dex_location, + const std::string& oat_location) { + uint32_t dex_location_checksum; + if (!DexFile::GetChecksum(dex_location, dex_location_checksum)) { + LOG(ERROR) << "Failed to compute checksum '" << dex_location << "'"; + return NULL; + } + + // Check if we already have an up-to-date output file + const DexFile* dex_file = FindDexFileInOatLocation(dex_location, + dex_location_checksum, + oat_location); + if (dex_file != NULL) { + return dex_file; + } + + // Generate the output oat file for the dex file + UniquePtr file(OS::OpenFile(oat_location.c_str(), true)); + if (file.get() == NULL) { + LOG(ERROR) << "Failed to create oat file: " << oat_location; + return NULL; + } + if (!GenerateOatFile(dex_location, file->Fd(), oat_location)) { + LOG(ERROR) << "Failed to generate oat file: " << oat_location; + return NULL; + } + const OatFile* oat_file = OatFile::Open(oat_location, oat_location, NULL); + if (oat_file == NULL) { + LOG(ERROR) << "Failed to open generated oat file: " << oat_location; + return NULL; + } + RegisterOatFileLocked(*oat_file); + const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location); + if (oat_dex_file == NULL) { + LOG(ERROR) << "Failed to find dex file in generated oat file: " << oat_location; + return NULL; + } + return oat_dex_file->OpenDexFile(); +} + +bool ClassLinker::VerifyOatFileChecksums(const OatFile* oat_file, + const std::string& dex_location, + uint32_t dex_location_checksum) { + Runtime* runtime = Runtime::Current(); + const ImageHeader& image_header = runtime->GetHeap()->GetImageSpace()->GetImageHeader(); + uint32_t image_oat_checksum = image_header.GetOatChecksum(); + uint32_t image_oat_data_begin = reinterpret_cast(image_header.GetOatDataBegin()); + bool image_check = ((oat_file->GetOatHeader().GetImageFileLocationOatChecksum() == image_oat_checksum) + && (oat_file->GetOatHeader().GetImageFileLocationOatDataBegin() == image_oat_data_begin)); + + const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location); + if (oat_dex_file == NULL) { + LOG(ERROR) << ".oat file " << oat_file->GetLocation() + << " does not contain contents for " << dex_location; + std::vector oat_dex_files = oat_file->GetOatDexFiles(); + for (size_t i = 0; i < oat_dex_files.size(); i++) { + const OatFile::OatDexFile* oat_dex_file = oat_dex_files[i]; + LOG(ERROR) << ".oat file " << oat_file->GetLocation() + << " contains contents for " << oat_dex_file->GetDexFileLocation(); + } + return false; + } + bool dex_check = dex_location_checksum == oat_dex_file->GetDexFileLocationChecksum(); + + if (image_check && dex_check) { + return true; + } + + if (!image_check) { + std::string image_file(image_header.GetImageRoot( + ImageHeader::kOatLocation)->AsString()->ToModifiedUtf8()); + LOG(WARNING) << ".oat file " << oat_file->GetLocation() + << " mismatch ( " << std::hex << oat_file->GetOatHeader().GetImageFileLocationOatChecksum() + << ", " << oat_file->GetOatHeader().GetImageFileLocationOatDataBegin() + << ") with " << image_file + << " (" << image_oat_checksum << ", " << std::hex << image_oat_data_begin << ")"; + } + if (!dex_check) { + LOG(WARNING) << ".oat file " << oat_file->GetLocation() + << " mismatch ( " << std::hex << oat_dex_file->GetDexFileLocationChecksum() + << ") with " << dex_location + << " (" << std::hex << dex_location_checksum << ")"; + } + return false; +} + +const DexFile* ClassLinker::VerifyAndOpenDexFileFromOatFile(const OatFile* oat_file, + const std::string& dex_location, + uint32_t dex_location_checksum) { + bool verified = VerifyOatFileChecksums(oat_file, dex_location, dex_location_checksum); + if (!verified) { + return NULL; + } + RegisterOatFileLocked(*oat_file); + return oat_file->GetOatDexFile(dex_location)->OpenDexFile(); +} + +const DexFile* ClassLinker::FindDexFileInOatFileFromDexLocation(const std::string& dex_location) { + MutexLock mu(Thread::Current(), dex_lock_); + + const OatFile* open_oat_file = FindOpenedOatFileFromDexLocation(dex_location); + if (open_oat_file != NULL) { + return open_oat_file->GetOatDexFile(dex_location)->OpenDexFile(); + } + + // Look for an existing file next to dex. for example, for + // /foo/bar/baz.jar, look for /foo/bar/baz.jar.oat. + std::string oat_filename(OatFile::DexFilenameToOatFilename(dex_location)); + const OatFile* oat_file = FindOatFileFromOatLocationLocked(oat_filename); + if (oat_file != NULL) { + uint32_t dex_location_checksum; + if (!DexFile::GetChecksum(dex_location, dex_location_checksum)) { + // If no classes.dex found in dex_location, it has been stripped, assume oat is up-to-date. + // This is the common case in user builds for jar's and apk's in the /system directory. + const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location); + CHECK(oat_dex_file != NULL) << oat_filename << " " << dex_location; + RegisterOatFileLocked(*oat_file); + return oat_dex_file->OpenDexFile(); + } + const DexFile* dex_file = VerifyAndOpenDexFileFromOatFile(oat_file, + dex_location, + dex_location_checksum); + if (dex_file != NULL) { + return dex_file; + } + } + // Look for an existing file in the art-cache, validating the result if found + // not found in /foo/bar/baz.oat? try /data/art-cache/foo@bar@baz.oat + std::string cache_location(GetArtCacheFilenameOrDie(oat_filename)); + oat_file = FindOatFileFromOatLocationLocked(cache_location); + if (oat_file != NULL) { + uint32_t dex_location_checksum; + if (!DexFile::GetChecksum(dex_location, dex_location_checksum)) { + LOG(WARNING) << "Failed to compute checksum: " << dex_location; + return NULL; + } + const DexFile* dex_file = VerifyAndOpenDexFileFromOatFile(oat_file, + dex_location, + dex_location_checksum); + if (dex_file != NULL) { + return dex_file; + } + if (TEMP_FAILURE_RETRY(unlink(oat_file->GetLocation().c_str())) != 0) { + PLOG(FATAL) << "Failed to remove obsolete .oat file " << oat_file->GetLocation(); + } + } + LOG(INFO) << "Failed to open oat file from " << oat_filename << " or " << cache_location << "."; + + // Try to generate oat file if it wasn't found or was obsolete. + std::string oat_cache_filename(GetArtCacheFilenameOrDie(oat_filename)); + return FindOrCreateOatFileForDexLocationLocked(dex_location, oat_cache_filename); +} + +const OatFile* ClassLinker::FindOpenedOatFileFromOatLocation(const std::string& oat_location) { + for (size_t i = 0; i < oat_files_.size(); i++) { + const OatFile* oat_file = oat_files_[i]; + DCHECK(oat_file != NULL); + if (oat_file->GetLocation() == oat_location) { + return oat_file; + } + } + return NULL; +} + +const OatFile* ClassLinker::FindOatFileFromOatLocation(const std::string& oat_location) { + MutexLock mu(Thread::Current(), dex_lock_); + return FindOatFileFromOatLocationLocked(oat_location); +} + +const OatFile* ClassLinker::FindOatFileFromOatLocationLocked(const std::string& oat_location) { + const OatFile* oat_file = FindOpenedOatFileFromOatLocation(oat_location); + if (oat_file != NULL) { + return oat_file; + } + + oat_file = OatFile::Open(oat_location, oat_location, NULL); + if (oat_file == NULL) { + return NULL; + } + return oat_file; +} + +void ClassLinker::InitFromImage() { + VLOG(startup) << "ClassLinker::InitFromImage entering"; + CHECK(!init_done_); + + Heap* heap = Runtime::Current()->GetHeap(); + ImageSpace* space = heap->GetImageSpace(); + OatFile* oat_file = OpenOat(space); + CHECK(oat_file != NULL) << "Failed to open oat file for image"; + CHECK_EQ(oat_file->GetOatHeader().GetImageFileLocationOatChecksum(), 0U); + CHECK_EQ(oat_file->GetOatHeader().GetImageFileLocationOatDataBegin(), 0U); + CHECK(oat_file->GetOatHeader().GetImageFileLocation().empty()); + mirror::Object* dex_caches_object = space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches); + mirror::ObjectArray* dex_caches = + dex_caches_object->AsObjectArray(); + + mirror::ObjectArray* class_roots = + space->GetImageHeader().GetImageRoot(ImageHeader::kClassRoots)->AsObjectArray(); + class_roots_ = class_roots; + + // Special case of setting up the String class early so that we can test arbitrary objects + // as being Strings or not + mirror::String::SetClass(GetClassRoot(kJavaLangString)); + + CHECK_EQ(oat_file->GetOatHeader().GetDexFileCount(), + static_cast(dex_caches->GetLength())); + Thread* self = Thread::Current(); + for (int i = 0; i < dex_caches->GetLength(); i++) { + SirtRef dex_cache(self, dex_caches->Get(i)); + const std::string& dex_file_location(dex_cache->GetLocation()->ToModifiedUtf8()); + const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_file_location); + CHECK(oat_dex_file != NULL) << oat_file->GetLocation() << " " << dex_file_location; + const DexFile* dex_file = oat_dex_file->OpenDexFile(); + if (dex_file == NULL) { + LOG(FATAL) << "Failed to open dex file " << dex_file_location + << " from within oat file " << oat_file->GetLocation(); + } + + CHECK_EQ(dex_file->GetLocationChecksum(), oat_dex_file->GetDexFileLocationChecksum()); + + AppendToBootClassPath(*dex_file, dex_cache); + } + + // Set classes on AbstractMethod early so that IsMethod tests can be performed during the live + // bitmap walk. + mirror::AbstractMethod::SetClasses(GetClassRoot(kJavaLangReflectConstructor), + GetClassRoot(kJavaLangReflectMethod)); + + // reinit clases_ table + { + ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_); + heap->FlushAllocStack(); + heap->GetLiveBitmap()->Walk(InitFromImageCallback, this); + } + + // reinit class_roots_ + mirror::Class::SetClassClass(class_roots->Get(kJavaLangClass)); + class_roots_ = class_roots; + + // reinit array_iftable_ from any array class instance, they should be == + array_iftable_ = GetClassRoot(kObjectArrayClass)->GetIfTable(); + DCHECK(array_iftable_ == GetClassRoot(kBooleanArrayClass)->GetIfTable()); + // String class root was set above + mirror::Field::SetClass(GetClassRoot(kJavaLangReflectField)); + mirror::BooleanArray::SetArrayClass(GetClassRoot(kBooleanArrayClass)); + mirror::ByteArray::SetArrayClass(GetClassRoot(kByteArrayClass)); + mirror::CharArray::SetArrayClass(GetClassRoot(kCharArrayClass)); + mirror::DoubleArray::SetArrayClass(GetClassRoot(kDoubleArrayClass)); + mirror::FloatArray::SetArrayClass(GetClassRoot(kFloatArrayClass)); + mirror::IntArray::SetArrayClass(GetClassRoot(kIntArrayClass)); + mirror::LongArray::SetArrayClass(GetClassRoot(kLongArrayClass)); + mirror::ShortArray::SetArrayClass(GetClassRoot(kShortArrayClass)); + mirror::Throwable::SetClass(GetClassRoot(kJavaLangThrowable)); + mirror::StackTraceElement::SetClass(GetClassRoot(kJavaLangStackTraceElement)); + + FinishInit(); + + VLOG(startup) << "ClassLinker::InitFromImage exiting"; +} + +void ClassLinker::InitFromImageCallback(mirror::Object* obj, void* arg) { + DCHECK(obj != NULL); + DCHECK(arg != NULL); + ClassLinker* class_linker = reinterpret_cast(arg); + + if (obj->GetClass()->IsStringClass()) { + class_linker->intern_table_->RegisterStrong(obj->AsString()); + return; + } + if (obj->IsClass()) { + // restore class to ClassLinker::classes_ table + mirror::Class* klass = obj->AsClass(); + ClassHelper kh(klass, class_linker); + mirror::Class* existing = class_linker->InsertClass(kh.GetDescriptor(), klass, true); + DCHECK(existing == NULL) << kh.GetDescriptor(); + return; + } + + // Check if object is a method without its code set and point it to the resolution trampoline. + if (obj->IsMethod()) { + mirror::AbstractMethod* method = obj->AsMethod(); + // Install entry point from interpreter. + if (method->GetEntryPointFromCompiledCode() == NULL && !method->IsNative() && !method->IsProxyMethod()) { + method->SetEntryPointFromInterpreter(interpreter::EnterInterpreterFromInterpreter); + } else { + method->SetEntryPointFromInterpreter(artInterpreterToQuickEntry); + } + if (method->GetEntryPointFromCompiledCode() == NULL) { + method->SetEntryPointFromCompiledCode(GetResolutionTrampoline()); + } + } +} + +// Keep in sync with InitCallback. Anything we visit, we need to +// reinit references to when reinitializing a ClassLinker from a +// mapped image. +void ClassLinker::VisitRoots(RootVisitor* visitor, void* arg) { + visitor(class_roots_, arg); + Thread* self = Thread::Current(); + { + MutexLock mu(self, dex_lock_); + for (size_t i = 0; i < dex_caches_.size(); i++) { + visitor(dex_caches_[i], arg); + } + } + + { + MutexLock mu(self, *Locks::classlinker_classes_lock_); + typedef Table::const_iterator It; // TODO: C++0x auto + for (It it = classes_.begin(), end = classes_.end(); it != end; ++it) { + visitor(it->second, arg); + } + + // We deliberately ignore the class roots in the image since we + // handle image roots by using the MS/CMS rescanning of dirty cards. + } + + visitor(array_iftable_, arg); + is_dirty_ = false; +} + +void ClassLinker::VisitClasses(ClassVisitor* visitor, void* arg) const { + MutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_); + typedef Table::const_iterator It; // TODO: C++0x auto + for (It it = classes_.begin(), end = classes_.end(); it != end; ++it) { + if (!visitor(it->second, arg)) { + return; + } + } + for (It it = image_classes_.begin(), end = image_classes_.end(); it != end; ++it) { + if (!visitor(it->second, arg)) { + return; + } + } +} + +static bool GetClassesVisitor(mirror::Class* c, void* arg) { + std::set* classes = reinterpret_cast*>(arg); + classes->insert(c); + return true; +} + +void ClassLinker::VisitClassesWithoutClassesLock(ClassVisitor* visitor, void* arg) const { + std::set classes; + VisitClasses(GetClassesVisitor, &classes); + typedef std::set::const_iterator It; // TODO: C++0x auto + for (It it = classes.begin(), end = classes.end(); it != end; ++it) { + if (!visitor(*it, arg)) { + return; + } + } +} + + +ClassLinker::~ClassLinker() { + mirror::Class::ResetClass(); + mirror::String::ResetClass(); + mirror::Field::ResetClass(); + mirror::AbstractMethod::ResetClasses(); + mirror::BooleanArray::ResetArrayClass(); + mirror::ByteArray::ResetArrayClass(); + mirror::CharArray::ResetArrayClass(); + mirror::DoubleArray::ResetArrayClass(); + mirror::FloatArray::ResetArrayClass(); + mirror::IntArray::ResetArrayClass(); + mirror::LongArray::ResetArrayClass(); + mirror::ShortArray::ResetArrayClass(); + mirror::Throwable::ResetClass(); + mirror::StackTraceElement::ResetClass(); + STLDeleteElements(&boot_class_path_); + STLDeleteElements(&oat_files_); +} + +mirror::DexCache* ClassLinker::AllocDexCache(Thread* self, const DexFile& dex_file) { + Heap* heap = Runtime::Current()->GetHeap(); + mirror::Class* dex_cache_class = GetClassRoot(kJavaLangDexCache); + SirtRef dex_cache(self, + down_cast(heap->AllocObject(self, dex_cache_class, + dex_cache_class->GetObjectSize()))); + if (dex_cache.get() == NULL) { + return NULL; + } + SirtRef + location(self, intern_table_->InternStrong(dex_file.GetLocation().c_str())); + if (location.get() == NULL) { + return NULL; + } + SirtRef > + strings(self, AllocStringArray(self, dex_file.NumStringIds())); + if (strings.get() == NULL) { + return NULL; + } + SirtRef > + types(self, AllocClassArray(self, dex_file.NumTypeIds())); + if (types.get() == NULL) { + return NULL; + } + SirtRef > + methods(self, AllocAbstractMethodArray(self, dex_file.NumMethodIds())); + if (methods.get() == NULL) { + return NULL; + } + SirtRef > + fields(self, AllocFieldArray(self, dex_file.NumFieldIds())); + if (fields.get() == NULL) { + return NULL; + } + SirtRef > + initialized_static_storage(self, + AllocObjectArray(self, dex_file.NumTypeIds())); + if (initialized_static_storage.get() == NULL) { + return NULL; + } + + dex_cache->Init(&dex_file, + location.get(), + strings.get(), + types.get(), + methods.get(), + fields.get(), + initialized_static_storage.get()); + return dex_cache.get(); +} + +mirror::Class* ClassLinker::AllocClass(Thread* self, mirror::Class* java_lang_Class, + size_t class_size) { + DCHECK_GE(class_size, sizeof(mirror::Class)); + Heap* heap = Runtime::Current()->GetHeap(); + SirtRef klass(self, + heap->AllocObject(self, java_lang_Class, class_size)->AsClass()); + klass->SetPrimitiveType(Primitive::kPrimNot); // default to not being primitive + klass->SetClassSize(class_size); + return klass.get(); +} + +mirror::Class* ClassLinker::AllocClass(Thread* self, size_t class_size) { + return AllocClass(self, GetClassRoot(kJavaLangClass), class_size); +} + +mirror::Field* ClassLinker::AllocField(Thread* self) { + return down_cast(GetClassRoot(kJavaLangReflectField)->AllocObject(self)); +} + +mirror::Method* ClassLinker::AllocMethod(Thread* self) { + return down_cast(GetClassRoot(kJavaLangReflectMethod)->AllocObject(self)); +} + +mirror::Constructor* ClassLinker::AllocConstructor(Thread* self) { + return down_cast(GetClassRoot(kJavaLangReflectConstructor)->AllocObject(self)); +} + +mirror::ObjectArray* ClassLinker::AllocStackTraceElementArray(Thread* self, + size_t length) { + return mirror::ObjectArray::Alloc(self, + GetClassRoot(kJavaLangStackTraceElementArrayClass), + length); +} + +static mirror::Class* EnsureResolved(Thread* self, mirror::Class* klass) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + DCHECK(klass != NULL); + // Wait for the class if it has not already been linked. + if (!klass->IsResolved() && !klass->IsErroneous()) { + ObjectLock lock(self, klass); + // Check for circular dependencies between classes. + if (!klass->IsResolved() && klass->GetClinitThreadId() == self->GetTid()) { + ThrowClassCircularityError(klass); + klass->SetStatus(mirror::Class::kStatusError); + return NULL; + } + // Wait for the pending initialization to complete. + while (!klass->IsResolved() && !klass->IsErroneous()) { + lock.WaitIgnoringInterrupts(); + } + } + if (klass->IsErroneous()) { + ThrowEarlierClassFailure(klass); + return NULL; + } + // Return the loaded class. No exceptions should be pending. + CHECK(klass->IsResolved()) << PrettyClass(klass); + self->AssertNoPendingException(); + return klass; +} + +mirror::Class* ClassLinker::FindSystemClass(const char* descriptor) { + return FindClass(descriptor, NULL); +} + +mirror::Class* ClassLinker::FindClass(const char* descriptor, mirror::ClassLoader* class_loader) { + DCHECK_NE(*descriptor, '\0') << "descriptor is empty string"; + Thread* self = Thread::Current(); + DCHECK(self != NULL); + self->AssertNoPendingException(); + if (descriptor[1] == '\0') { + // only the descriptors of primitive types should be 1 character long, also avoid class lookup + // for primitive classes that aren't backed by dex files. + return FindPrimitiveClass(descriptor[0]); + } + // Find the class in the loaded classes table. + mirror::Class* klass = LookupClass(descriptor, class_loader); + if (klass != NULL) { + return EnsureResolved(self, klass); + } + // Class is not yet loaded. + if (descriptor[0] == '[') { + return CreateArrayClass(descriptor, class_loader); + + } else if (class_loader == NULL) { + DexFile::ClassPathEntry pair = DexFile::FindInClassPath(descriptor, boot_class_path_); + if (pair.second != NULL) { + return DefineClass(descriptor, NULL, *pair.first, *pair.second); + } + + } else if (Runtime::Current()->UseCompileTimeClassPath()) { + // first try the boot class path + mirror::Class* system_class = FindSystemClass(descriptor); + if (system_class != NULL) { + return system_class; + } + CHECK(self->IsExceptionPending()); + self->ClearException(); + + // next try the compile time class path + const std::vector* class_path; + { + ScopedObjectAccessUnchecked soa(Thread::Current()); + ScopedLocalRef jclass_loader(soa.Env(), soa.AddLocalReference(class_loader)); + class_path = &Runtime::Current()->GetCompileTimeClassPath(jclass_loader.get()); + } + + DexFile::ClassPathEntry pair = DexFile::FindInClassPath(descriptor, *class_path); + if (pair.second != NULL) { + return DefineClass(descriptor, class_loader, *pair.first, *pair.second); + } + + } else { + ScopedObjectAccessUnchecked soa(self->GetJniEnv()); + ScopedLocalRef class_loader_object(soa.Env(), + soa.AddLocalReference(class_loader)); + std::string class_name_string(DescriptorToDot(descriptor)); + ScopedLocalRef result(soa.Env(), NULL); + { + ScopedThreadStateChange tsc(self, kNative); + ScopedLocalRef class_name_object(soa.Env(), + soa.Env()->NewStringUTF(class_name_string.c_str())); + if (class_name_object.get() == NULL) { + return NULL; + } + CHECK(class_loader_object.get() != NULL); + result.reset(soa.Env()->CallObjectMethod(class_loader_object.get(), + WellKnownClasses::java_lang_ClassLoader_loadClass, + class_name_object.get())); + } + if (soa.Self()->IsExceptionPending()) { + // If the ClassLoader threw, pass that exception up. + return NULL; + } else if (result.get() == NULL) { + // broken loader - throw NPE to be compatible with Dalvik + ThrowNullPointerException(NULL, StringPrintf("ClassLoader.loadClass returned null for %s", + class_name_string.c_str()).c_str()); + return NULL; + } else { + // success, return mirror::Class* + return soa.Decode(result.get()); + } + } + + ThrowNoClassDefFoundError("Class %s not found", PrintableString(descriptor).c_str()); + return NULL; +} + +mirror::Class* ClassLinker::DefineClass(const StringPiece& descriptor, + mirror::ClassLoader* class_loader, + const DexFile& dex_file, + const DexFile::ClassDef& dex_class_def) { + Thread* self = Thread::Current(); + SirtRef klass(self, NULL); + // Load the class from the dex file. + if (!init_done_) { + // finish up init of hand crafted class_roots_ + if (descriptor == "Ljava/lang/Object;") { + klass.reset(GetClassRoot(kJavaLangObject)); + } else if (descriptor == "Ljava/lang/Class;") { + klass.reset(GetClassRoot(kJavaLangClass)); + } else if (descriptor == "Ljava/lang/String;") { + klass.reset(GetClassRoot(kJavaLangString)); + } else if (descriptor == "Ljava/lang/DexCache;") { + klass.reset(GetClassRoot(kJavaLangDexCache)); + } else if (descriptor == "Ljava/lang/reflect/Field;") { + klass.reset(GetClassRoot(kJavaLangReflectField)); + } else if (descriptor == "Ljava/lang/reflect/AbstractMethod;") { + klass.reset(GetClassRoot(kJavaLangReflectAbstractMethod)); + } else if (descriptor == "Ljava/lang/reflect/Constructor;") { + klass.reset(GetClassRoot(kJavaLangReflectConstructor)); + } else if (descriptor == "Ljava/lang/reflect/Method;") { + klass.reset(GetClassRoot(kJavaLangReflectMethod)); + } else { + klass.reset(AllocClass(self, SizeOfClass(dex_file, dex_class_def))); + } + } else { + klass.reset(AllocClass(self, SizeOfClass(dex_file, dex_class_def))); + } + klass->SetDexCache(FindDexCache(dex_file)); + LoadClass(dex_file, dex_class_def, klass, class_loader); + // Check for a pending exception during load + if (self->IsExceptionPending()) { + klass->SetStatus(mirror::Class::kStatusError); + return NULL; + } + ObjectLock lock(self, klass.get()); + klass->SetClinitThreadId(self->GetTid()); + // Add the newly loaded class to the loaded classes table. + SirtRef existing(self, InsertClass(descriptor, klass.get(), false)); + if (existing.get() != NULL) { + // We failed to insert because we raced with another thread. + return EnsureResolved(self, existing.get()); + } + // Finish loading (if necessary) by finding parents + CHECK(!klass->IsLoaded()); + if (!LoadSuperAndInterfaces(klass, dex_file)) { + // Loading failed. + klass->SetStatus(mirror::Class::kStatusError); + lock.NotifyAll(); + return NULL; + } + CHECK(klass->IsLoaded()); + // Link the class (if necessary) + CHECK(!klass->IsResolved()); + if (!LinkClass(klass, NULL)) { + // Linking failed. + klass->SetStatus(mirror::Class::kStatusError); + lock.NotifyAll(); + return NULL; + } + CHECK(klass->IsResolved()); + + /* + * We send CLASS_PREPARE events to the debugger from here. The + * definition of "preparation" is creating the static fields for a + * class and initializing them to the standard default values, but not + * executing any code (that comes later, during "initialization"). + * + * We did the static preparation in LinkClass. + * + * The class has been prepared and resolved but possibly not yet verified + * at this point. + */ + Dbg::PostClassPrepare(klass.get()); + + return klass.get(); +} + +// Precomputes size that will be needed for Class, matching LinkStaticFields +size_t ClassLinker::SizeOfClass(const DexFile& dex_file, + const DexFile::ClassDef& dex_class_def) { + const byte* class_data = dex_file.GetClassData(dex_class_def); + size_t num_ref = 0; + size_t num_32 = 0; + size_t num_64 = 0; + if (class_data != NULL) { + for (ClassDataItemIterator it(dex_file, class_data); it.HasNextStaticField(); it.Next()) { + const DexFile::FieldId& field_id = dex_file.GetFieldId(it.GetMemberIndex()); + const char* descriptor = dex_file.GetFieldTypeDescriptor(field_id); + char c = descriptor[0]; + if (c == 'L' || c == '[') { + num_ref++; + } else if (c == 'J' || c == 'D') { + num_64++; + } else { + num_32++; + } + } + } + // start with generic class data + size_t size = sizeof(mirror::Class); + // follow with reference fields which must be contiguous at start + size += (num_ref * sizeof(uint32_t)); + // if there are 64-bit fields to add, make sure they are aligned + if (num_64 != 0 && size != RoundUp(size, 8)) { // for 64-bit alignment + if (num_32 != 0) { + // use an available 32-bit field for padding + num_32--; + } + size += sizeof(uint32_t); // either way, we are adding a word + DCHECK_EQ(size, RoundUp(size, 8)); + } + // tack on any 64-bit fields now that alignment is assured + size += (num_64 * sizeof(uint64_t)); + // tack on any remaining 32-bit fields + size += (num_32 * sizeof(uint32_t)); + return size; +} + +const OatFile::OatClass* ClassLinker::GetOatClass(const DexFile& dex_file, const char* descriptor) { + DCHECK(descriptor != NULL); + const OatFile* oat_file = FindOpenedOatFileForDexFile(dex_file); + CHECK(oat_file != NULL) << dex_file.GetLocation() << " " << descriptor; + const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_file.GetLocation()); + CHECK(oat_dex_file != NULL) << dex_file.GetLocation() << " " << descriptor; + uint32_t class_def_index; + bool found = dex_file.FindClassDefIndex(descriptor, class_def_index); + CHECK(found) << dex_file.GetLocation() << " " << descriptor; + const OatFile::OatClass* oat_class = oat_dex_file->GetOatClass(class_def_index); + CHECK(oat_class != NULL) << dex_file.GetLocation() << " " << descriptor; + return oat_class; +} + +static uint32_t GetOatMethodIndexFromMethodIndex(const DexFile& dex_file, uint32_t method_idx) { + const DexFile::MethodId& method_id = dex_file.GetMethodId(method_idx); + const DexFile::TypeId& type_id = dex_file.GetTypeId(method_id.class_idx_); + const DexFile::ClassDef* class_def = dex_file.FindClassDef(dex_file.GetTypeDescriptor(type_id)); + CHECK(class_def != NULL); + const byte* class_data = dex_file.GetClassData(*class_def); + CHECK(class_data != NULL); + ClassDataItemIterator it(dex_file, class_data); + // Skip fields + while (it.HasNextStaticField()) { + it.Next(); + } + while (it.HasNextInstanceField()) { + it.Next(); + } + // Process methods + size_t class_def_method_index = 0; + while (it.HasNextDirectMethod()) { + if (it.GetMemberIndex() == method_idx) { + return class_def_method_index; + } + class_def_method_index++; + it.Next(); + } + while (it.HasNextVirtualMethod()) { + if (it.GetMemberIndex() == method_idx) { + return class_def_method_index; + } + class_def_method_index++; + it.Next(); + } + DCHECK(!it.HasNext()); + LOG(FATAL) << "Failed to find method index " << method_idx << " in " << dex_file.GetLocation(); + return 0; +} + +const OatFile::OatMethod ClassLinker::GetOatMethodFor(const mirror::AbstractMethod* method) { + // Although we overwrite the trampoline of non-static methods, we may get here via the resolution + // method for direct methods (or virtual methods made direct). + mirror::Class* declaring_class = method->GetDeclaringClass(); + size_t oat_method_index; + if (method->IsStatic() || method->IsDirect()) { + // Simple case where the oat method index was stashed at load time. + oat_method_index = method->GetMethodIndex(); + } else { + // We're invoking a virtual method directly (thanks to sharpening), compute the oat_method_index + // by search for its position in the declared virtual methods. + oat_method_index = declaring_class->NumDirectMethods(); + size_t end = declaring_class->NumVirtualMethods(); + bool found = false; + for (size_t i = 0; i < end; i++) { + if (declaring_class->GetVirtualMethod(i) == method) { + found = true; + break; + } + oat_method_index++; + } + CHECK(found) << "Didn't find oat method index for virtual method: " << PrettyMethod(method); + } + ClassHelper kh(declaring_class); + UniquePtr oat_class(GetOatClass(kh.GetDexFile(), kh.GetDescriptor())); + CHECK(oat_class.get() != NULL); + DCHECK_EQ(oat_method_index, + GetOatMethodIndexFromMethodIndex(*declaring_class->GetDexCache()->GetDexFile(), + method->GetDexMethodIndex())); + + return oat_class->GetOatMethod(oat_method_index); +} + +// Special case to get oat code without overwriting a trampoline. +const void* ClassLinker::GetOatCodeFor(const mirror::AbstractMethod* method) { + CHECK(!method->IsAbstract()) << PrettyMethod(method); + const void* result = GetOatMethodFor(method).GetCode(); + if (result == NULL) { + // No code? You must mean to go into the interpreter. + result = GetInterpreterEntryPoint(); + } + return result; +} + +const void* ClassLinker::GetOatCodeFor(const DexFile& dex_file, uint32_t method_idx) { + const DexFile::MethodId& method_id = dex_file.GetMethodId(method_idx); + const char* descriptor = dex_file.GetTypeDescriptor(dex_file.GetTypeId(method_id.class_idx_)); + uint32_t oat_method_idx = GetOatMethodIndexFromMethodIndex(dex_file, method_idx); + UniquePtr oat_class(GetOatClass(dex_file, descriptor)); + CHECK(oat_class.get() != NULL); + return oat_class->GetOatMethod(oat_method_idx).GetCode(); +} + +void ClassLinker::FixupStaticTrampolines(mirror::Class* klass) { + ClassHelper kh(klass); + const DexFile::ClassDef* dex_class_def = kh.GetClassDef(); + CHECK(dex_class_def != NULL); + const DexFile& dex_file = kh.GetDexFile(); + const byte* class_data = dex_file.GetClassData(*dex_class_def); + if (class_data == NULL) { + return; // no fields or methods - for example a marker interface + } + Runtime* runtime = Runtime::Current(); + if (!runtime->IsStarted() || runtime->UseCompileTimeClassPath()) { + // OAT file unavailable + return; + } + UniquePtr oat_class(GetOatClass(dex_file, kh.GetDescriptor())); + CHECK(oat_class.get() != NULL); + ClassDataItemIterator it(dex_file, class_data); + // Skip fields + while (it.HasNextStaticField()) { + it.Next(); + } + while (it.HasNextInstanceField()) { + it.Next(); + } + size_t method_index = 0; + // Link the code of methods skipped by LinkCode + for (size_t i = 0; it.HasNextDirectMethod(); i++, it.Next()) { + mirror::AbstractMethod* method = klass->GetDirectMethod(i); + if (method->IsStatic()) { + const void* code = oat_class->GetOatMethod(method_index).GetCode(); + if (code == NULL) { + // No code? You must mean to go into the interpreter. + code = GetInterpreterEntryPoint(); + } + runtime->GetInstrumentation()->UpdateMethodsCode(method, code); + } + method_index++; + } + // Ignore virtual methods on the iterator. +} + +static void LinkCode(SirtRef& method, const OatFile::OatClass* oat_class, + uint32_t method_index) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + // Method shouldn't have already been linked. + DCHECK(method->GetEntryPointFromCompiledCode() == NULL); + // Every kind of method should at least get an invoke stub from the oat_method. + // non-abstract methods also get their code pointers. + const OatFile::OatMethod oat_method = oat_class->GetOatMethod(method_index); + oat_method.LinkMethod(method.get()); + + // Install entry point from interpreter. + if (method->GetEntryPointFromCompiledCode() == NULL && !method->IsNative() && + !method->IsProxyMethod()) { + method->SetEntryPointFromInterpreter(interpreter::EnterInterpreterFromInterpreter); + } else { + method->SetEntryPointFromInterpreter(artInterpreterToQuickEntry); + } + + Runtime* runtime = Runtime::Current(); + if (method->IsAbstract()) { + method->SetEntryPointFromCompiledCode(GetAbstractMethodErrorStub()); + return; + } + + if (method->IsStatic() && !method->IsConstructor()) { + // For static methods excluding the class initializer, install the trampoline. + method->SetEntryPointFromCompiledCode(GetResolutionTrampoline()); + } + + if (method->IsNative()) { + // Unregistering restores the dlsym lookup stub. + method->UnregisterNative(Thread::Current()); + } + + if (method->GetEntryPointFromCompiledCode() == NULL) { + // No code? You must mean to go into the interpreter. + method->SetEntryPointFromCompiledCode(GetInterpreterEntryPoint()); + } + + // Allow instrumentation its chance to hijack code. + runtime->GetInstrumentation()->UpdateMethodsCode(method.get(), + method->GetEntryPointFromCompiledCode()); +} + +void ClassLinker::LoadClass(const DexFile& dex_file, + const DexFile::ClassDef& dex_class_def, + SirtRef& klass, + mirror::ClassLoader* class_loader) { + CHECK(klass.get() != NULL); + CHECK(klass->GetDexCache() != NULL); + CHECK_EQ(mirror::Class::kStatusNotReady, klass->GetStatus()); + const char* descriptor = dex_file.GetClassDescriptor(dex_class_def); + CHECK(descriptor != NULL); + + klass->SetClass(GetClassRoot(kJavaLangClass)); + uint32_t access_flags = dex_class_def.access_flags_; + // Make sure that none of our runtime-only flags are set. + // TODO: JACK CLASS ACCESS (HACK TO BE REMOVED) + CHECK_EQ(access_flags & ~(kAccJavaFlagsMask | kAccClassJack), 0U); + klass->SetAccessFlags(access_flags); + klass->SetClassLoader(class_loader); + DCHECK_EQ(klass->GetPrimitiveType(), Primitive::kPrimNot); + klass->SetStatus(mirror::Class::kStatusIdx); + + klass->SetDexTypeIndex(dex_class_def.class_idx_); + + // Load fields fields. + const byte* class_data = dex_file.GetClassData(dex_class_def); + if (class_data == NULL) { + return; // no fields or methods - for example a marker interface + } + ClassDataItemIterator it(dex_file, class_data); + Thread* self = Thread::Current(); + if (it.NumStaticFields() != 0) { + klass->SetSFields(AllocFieldArray(self, it.NumStaticFields())); + } + if (it.NumInstanceFields() != 0) { + klass->SetIFields(AllocFieldArray(self, it.NumInstanceFields())); + } + for (size_t i = 0; it.HasNextStaticField(); i++, it.Next()) { + SirtRef sfield(self, AllocField(self)); + klass->SetStaticField(i, sfield.get()); + LoadField(dex_file, it, klass, sfield); + } + for (size_t i = 0; it.HasNextInstanceField(); i++, it.Next()) { + SirtRef ifield(self, AllocField(self)); + klass->SetInstanceField(i, ifield.get()); + LoadField(dex_file, it, klass, ifield); + } + + UniquePtr oat_class; + if (Runtime::Current()->IsStarted() && !Runtime::Current()->UseCompileTimeClassPath()) { + oat_class.reset(GetOatClass(dex_file, descriptor)); + } + + // Load methods. + if (it.NumDirectMethods() != 0) { + // TODO: append direct methods to class object + klass->SetDirectMethods(AllocAbstractMethodArray(self, it.NumDirectMethods())); + } + if (it.NumVirtualMethods() != 0) { + // TODO: append direct methods to class object + klass->SetVirtualMethods(AllocMethodArray(self, it.NumVirtualMethods())); + } + size_t class_def_method_index = 0; + for (size_t i = 0; it.HasNextDirectMethod(); i++, it.Next()) { + SirtRef method(self, LoadMethod(self, dex_file, it, klass)); + klass->SetDirectMethod(i, method.get()); + if (oat_class.get() != NULL) { + LinkCode(method, oat_class.get(), class_def_method_index); + } + method->SetMethodIndex(class_def_method_index); + class_def_method_index++; + } + for (size_t i = 0; it.HasNextVirtualMethod(); i++, it.Next()) { + SirtRef method(self, LoadMethod(self, dex_file, it, klass)); + klass->SetVirtualMethod(i, method.get()); + DCHECK_EQ(class_def_method_index, it.NumDirectMethods() + i); + if (oat_class.get() != NULL) { + LinkCode(method, oat_class.get(), class_def_method_index); + } + class_def_method_index++; + } + DCHECK(!it.HasNext()); +} + +void ClassLinker::LoadField(const DexFile& /*dex_file*/, const ClassDataItemIterator& it, + SirtRef& klass, SirtRef& dst) { + uint32_t field_idx = it.GetMemberIndex(); + dst->SetDexFieldIndex(field_idx); + dst->SetDeclaringClass(klass.get()); + dst->SetAccessFlags(it.GetMemberAccessFlags()); +} + +mirror::AbstractMethod* ClassLinker::LoadMethod(Thread* self, const DexFile& dex_file, + const ClassDataItemIterator& it, + SirtRef& klass) { + uint32_t dex_method_idx = it.GetMemberIndex(); + const DexFile::MethodId& method_id = dex_file.GetMethodId(dex_method_idx); + StringPiece method_name(dex_file.GetMethodName(method_id)); + + mirror::AbstractMethod* dst = NULL; + if (method_name == "") { + dst = AllocConstructor(self); + } else { + dst = AllocMethod(self); + } + DCHECK(dst->IsMethod()) << PrettyDescriptor(dst->GetClass()); + + const char* old_cause = self->StartAssertNoThreadSuspension("LoadMethod"); + dst->SetDexMethodIndex(dex_method_idx); + dst->SetDeclaringClass(klass.get()); + + if (method_name == "finalize") { + // Create the prototype for a signature of "()V" + const DexFile::StringId* void_string_id = dex_file.FindStringId("V"); + if (void_string_id != NULL) { + const DexFile::TypeId* void_type_id = + dex_file.FindTypeId(dex_file.GetIndexForStringId(*void_string_id)); + if (void_type_id != NULL) { + std::vector no_args; + const DexFile::ProtoId* finalizer_proto = + dex_file.FindProtoId(dex_file.GetIndexForTypeId(*void_type_id), no_args); + if (finalizer_proto != NULL) { + // We have the prototype in the dex file + if (klass->GetClassLoader() != NULL) { // All non-boot finalizer methods are flagged + klass->SetFinalizable(); + } else { + StringPiece klass_descriptor(dex_file.StringByTypeIdx(klass->GetDexTypeIndex())); + // The Enum class declares a "final" finalize() method to prevent subclasses from + // introducing a finalizer. We don't want to set the finalizable flag for Enum or its + // subclasses, so we exclude it here. + // We also want to avoid setting the flag on Object, where we know that finalize() is + // empty. + if (klass_descriptor != "Ljava/lang/Object;" && + klass_descriptor != "Ljava/lang/Enum;") { + klass->SetFinalizable(); + } + } + } + } + } + } + dst->SetCodeItemOffset(it.GetMethodCodeItemOffset()); + dst->SetAccessFlags(it.GetMemberAccessFlags()); + + dst->SetDexCacheStrings(klass->GetDexCache()->GetStrings()); + dst->SetDexCacheResolvedMethods(klass->GetDexCache()->GetResolvedMethods()); + dst->SetDexCacheResolvedTypes(klass->GetDexCache()->GetResolvedTypes()); + dst->SetDexCacheInitializedStaticStorage(klass->GetDexCache()->GetInitializedStaticStorage()); + + CHECK(dst->IsMethod()); + + self->EndAssertNoThreadSuspension(old_cause); + return dst; +} + +void ClassLinker::AppendToBootClassPath(const DexFile& dex_file) { + Thread* self = Thread::Current(); + SirtRef dex_cache(self, AllocDexCache(self, dex_file)); + AppendToBootClassPath(dex_file, dex_cache); +} + +void ClassLinker::AppendToBootClassPath(const DexFile& dex_file, SirtRef& dex_cache) { + CHECK(dex_cache.get() != NULL) << dex_file.GetLocation(); + boot_class_path_.push_back(&dex_file); + RegisterDexFile(dex_file, dex_cache); +} + +bool ClassLinker::IsDexFileRegisteredLocked(const DexFile& dex_file) const { + dex_lock_.AssertHeld(Thread::Current()); + for (size_t i = 0; i != dex_caches_.size(); ++i) { + if (dex_caches_[i]->GetDexFile() == &dex_file) { + return true; + } + } + return false; +} + +bool ClassLinker::IsDexFileRegistered(const DexFile& dex_file) const { + MutexLock mu(Thread::Current(), dex_lock_); + return IsDexFileRegisteredLocked(dex_file); +} + +void ClassLinker::RegisterDexFileLocked(const DexFile& dex_file, SirtRef& dex_cache) { + dex_lock_.AssertHeld(Thread::Current()); + CHECK(dex_cache.get() != NULL) << dex_file.GetLocation(); + CHECK(dex_cache->GetLocation()->Equals(dex_file.GetLocation())); + dex_caches_.push_back(dex_cache.get()); + dex_cache->SetDexFile(&dex_file); + Dirty(); +} + +void ClassLinker::RegisterDexFile(const DexFile& dex_file) { + Thread* self = Thread::Current(); + { + MutexLock mu(self, dex_lock_); + if (IsDexFileRegisteredLocked(dex_file)) { + return; + } + } + // Don't alloc while holding the lock, since allocation may need to + // suspend all threads and another thread may need the dex_lock_ to + // get to a suspend point. + SirtRef dex_cache(self, AllocDexCache(self, dex_file)); + { + MutexLock mu(self, dex_lock_); + if (IsDexFileRegisteredLocked(dex_file)) { + return; + } + RegisterDexFileLocked(dex_file, dex_cache); + } +} + +void ClassLinker::RegisterDexFile(const DexFile& dex_file, SirtRef& dex_cache) { + MutexLock mu(Thread::Current(), dex_lock_); + RegisterDexFileLocked(dex_file, dex_cache); +} + +mirror::DexCache* ClassLinker::FindDexCache(const DexFile& dex_file) const { + MutexLock mu(Thread::Current(), dex_lock_); + // Search assuming unique-ness of dex file. + for (size_t i = 0; i != dex_caches_.size(); ++i) { + mirror::DexCache* dex_cache = dex_caches_[i]; + if (dex_cache->GetDexFile() == &dex_file) { + return dex_cache; + } + } + // Search matching by location name. + std::string location(dex_file.GetLocation()); + for (size_t i = 0; i != dex_caches_.size(); ++i) { + mirror::DexCache* dex_cache = dex_caches_[i]; + if (dex_cache->GetDexFile()->GetLocation() == location) { + return dex_cache; + } + } + // Failure, dump diagnostic and abort. + for (size_t i = 0; i != dex_caches_.size(); ++i) { + mirror::DexCache* dex_cache = dex_caches_[i]; + LOG(ERROR) << "Registered dex file " << i << " = " << dex_cache->GetDexFile()->GetLocation(); + } + LOG(FATAL) << "Failed to find DexCache for DexFile " << location; + return NULL; +} + +void ClassLinker::FixupDexCaches(mirror::AbstractMethod* resolution_method) const { + MutexLock mu(Thread::Current(), dex_lock_); + for (size_t i = 0; i != dex_caches_.size(); ++i) { + dex_caches_[i]->Fixup(resolution_method); + } +} + +mirror::Class* ClassLinker::CreatePrimitiveClass(Thread* self, Primitive::Type type) { + return InitializePrimitiveClass(AllocClass(self, sizeof(mirror::Class)), type); +} + +mirror::Class* ClassLinker::InitializePrimitiveClass(mirror::Class* primitive_class, Primitive::Type type) { + CHECK(primitive_class != NULL); + // Must hold lock on object when initializing. + ObjectLock lock(Thread::Current(), primitive_class); + primitive_class->SetAccessFlags(kAccPublic | kAccFinal | kAccAbstract); + primitive_class->SetPrimitiveType(type); + primitive_class->SetStatus(mirror::Class::kStatusInitialized); + mirror::Class* existing = InsertClass(Primitive::Descriptor(type), primitive_class, false); + CHECK(existing == NULL) << "InitPrimitiveClass(" << type << ") failed"; + return primitive_class; +} + +// Create an array class (i.e. the class object for the array, not the +// array itself). "descriptor" looks like "[C" or "[[[[B" or +// "[Ljava/lang/String;". +// +// If "descriptor" refers to an array of primitives, look up the +// primitive type's internally-generated class object. +// +// "class_loader" is the class loader of the class that's referring to +// us. It's used to ensure that we're looking for the element type in +// the right context. It does NOT become the class loader for the +// array class; that always comes from the base element class. +// +// Returns NULL with an exception raised on failure. +mirror::Class* ClassLinker::CreateArrayClass(const std::string& descriptor, + mirror::ClassLoader* class_loader) { + CHECK_EQ('[', descriptor[0]); + + // Identify the underlying component type + mirror::Class* component_type = FindClass(descriptor.substr(1).c_str(), class_loader); + if (component_type == NULL) { + DCHECK(Thread::Current()->IsExceptionPending()); + return NULL; + } + + // See if the component type is already loaded. Array classes are + // always associated with the class loader of their underlying + // element type -- an array of Strings goes with the loader for + // java/lang/String -- so we need to look for it there. (The + // caller should have checked for the existence of the class + // before calling here, but they did so with *their* class loader, + // not the component type's loader.) + // + // If we find it, the caller adds "loader" to the class' initiating + // loader list, which should prevent us from going through this again. + // + // This call is unnecessary if "loader" and "component_type->GetClassLoader()" + // are the same, because our caller (FindClass) just did the + // lookup. (Even if we get this wrong we still have correct behavior, + // because we effectively do this lookup again when we add the new + // class to the hash table --- necessary because of possible races with + // other threads.) + if (class_loader != component_type->GetClassLoader()) { + mirror::Class* new_class = LookupClass(descriptor.c_str(), component_type->GetClassLoader()); + if (new_class != NULL) { + return new_class; + } + } + + // Fill out the fields in the Class. + // + // It is possible to execute some methods against arrays, because + // all arrays are subclasses of java_lang_Object_, so we need to set + // up a vtable. We can just point at the one in java_lang_Object_. + // + // Array classes are simple enough that we don't need to do a full + // link step. + Thread* self = Thread::Current(); + SirtRef new_class(self, NULL); + if (!init_done_) { + // Classes that were hand created, ie not by FindSystemClass + if (descriptor == "[Ljava/lang/Class;") { + new_class.reset(GetClassRoot(kClassArrayClass)); + } else if (descriptor == "[Ljava/lang/Object;") { + new_class.reset(GetClassRoot(kObjectArrayClass)); + } else if (descriptor == class_roots_descriptors_[kJavaLangStringArrayClass]) { + new_class.reset(GetClassRoot(kJavaLangStringArrayClass)); + } else if (descriptor == class_roots_descriptors_[kJavaLangReflectAbstractMethodArrayClass]) { + new_class.reset(GetClassRoot(kJavaLangReflectAbstractMethodArrayClass)); + } else if (descriptor == class_roots_descriptors_[kJavaLangReflectFieldArrayClass]) { + new_class.reset(GetClassRoot(kJavaLangReflectFieldArrayClass)); + } else if (descriptor == class_roots_descriptors_[kJavaLangReflectMethodArrayClass]) { + new_class.reset(GetClassRoot(kJavaLangReflectMethodArrayClass)); + } else if (descriptor == "[C") { + new_class.reset(GetClassRoot(kCharArrayClass)); + } else if (descriptor == "[I") { + new_class.reset(GetClassRoot(kIntArrayClass)); + } + } + if (new_class.get() == NULL) { + new_class.reset(AllocClass(self, sizeof(mirror::Class))); + if (new_class.get() == NULL) { + return NULL; + } + new_class->SetComponentType(component_type); + } + ObjectLock lock(self, new_class.get()); // Must hold lock on object when initializing. + DCHECK(new_class->GetComponentType() != NULL); + mirror::Class* java_lang_Object = GetClassRoot(kJavaLangObject); + new_class->SetSuperClass(java_lang_Object); + new_class->SetVTable(java_lang_Object->GetVTable()); + new_class->SetPrimitiveType(Primitive::kPrimNot); + new_class->SetClassLoader(component_type->GetClassLoader()); + new_class->SetStatus(mirror::Class::kStatusInitialized); + // don't need to set new_class->SetObjectSize(..) + // because Object::SizeOf delegates to Array::SizeOf + + + // All arrays have java/lang/Cloneable and java/io/Serializable as + // interfaces. We need to set that up here, so that stuff like + // "instanceof" works right. + // + // Note: The GC could run during the call to FindSystemClass, + // so we need to make sure the class object is GC-valid while we're in + // there. Do this by clearing the interface list so the GC will just + // think that the entries are null. + + + // Use the single, global copies of "interfaces" and "iftable" + // (remember not to free them for arrays). + CHECK(array_iftable_ != NULL); + new_class->SetIfTable(array_iftable_); + + // Inherit access flags from the component type. Arrays can't be + // used as a superclass or interface, so we want to add "final" + // and remove "interface". + // + // Don't inherit any non-standard flags (e.g., kAccFinal) + // from component_type. We assume that the array class does not + // override finalize(). + new_class->SetAccessFlags(((new_class->GetComponentType()->GetAccessFlags() & + ~kAccInterface) | kAccFinal) & kAccJavaFlagsMask); + + mirror::Class* existing = InsertClass(descriptor, new_class.get(), false); + if (existing == NULL) { + return new_class.get(); + } + // Another thread must have loaded the class after we + // started but before we finished. Abandon what we've + // done. + // + // (Yes, this happens.) + + return existing; +} + +mirror::Class* ClassLinker::FindPrimitiveClass(char type) { + switch (Primitive::GetType(type)) { + case Primitive::kPrimByte: + return GetClassRoot(kPrimitiveByte); + case Primitive::kPrimChar: + return GetClassRoot(kPrimitiveChar); + case Primitive::kPrimDouble: + return GetClassRoot(kPrimitiveDouble); + case Primitive::kPrimFloat: + return GetClassRoot(kPrimitiveFloat); + case Primitive::kPrimInt: + return GetClassRoot(kPrimitiveInt); + case Primitive::kPrimLong: + return GetClassRoot(kPrimitiveLong); + case Primitive::kPrimShort: + return GetClassRoot(kPrimitiveShort); + case Primitive::kPrimBoolean: + return GetClassRoot(kPrimitiveBoolean); + case Primitive::kPrimVoid: + return GetClassRoot(kPrimitiveVoid); + case Primitive::kPrimNot: + break; + } + std::string printable_type(PrintableChar(type)); + ThrowNoClassDefFoundError("Not a primitive type: %s", printable_type.c_str()); + return NULL; +} + +mirror::Class* ClassLinker::InsertClass(const StringPiece& descriptor, mirror::Class* klass, bool image_class) { + if (VLOG_IS_ON(class_linker)) { + mirror::DexCache* dex_cache = klass->GetDexCache(); + std::string source; + if (dex_cache != NULL) { + source += " from "; + source += dex_cache->GetLocation()->ToModifiedUtf8(); + } + LOG(INFO) << "Loaded class " << descriptor << source; + } + size_t hash = StringPieceHash()(descriptor); + MutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_); + Table& classes = image_class ? image_classes_ : classes_; + mirror::Class* existing = LookupClassLocked(descriptor.data(), klass->GetClassLoader(), hash, classes); +#ifndef NDEBUG + // Check we don't have the class in the other table in error + Table& other_classes = image_class ? classes_ : image_classes_; + CHECK(LookupClassLocked(descriptor.data(), klass->GetClassLoader(), hash, other_classes) == NULL); +#endif + if (existing != NULL) { + return existing; + } + classes.insert(std::make_pair(hash, klass)); + Dirty(); + return NULL; +} + +bool ClassLinker::RemoveClass(const char* descriptor, const mirror::ClassLoader* class_loader) { + size_t hash = Hash(descriptor); + MutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_); + typedef Table::iterator It; // TODO: C++0x auto + // TODO: determine if its better to search classes_ or image_classes_ first + ClassHelper kh; + for (It it = classes_.lower_bound(hash), end = classes_.end(); it != end && it->first == hash; + ++it) { + mirror::Class* klass = it->second; + kh.ChangeClass(klass); + if (strcmp(kh.GetDescriptor(), descriptor) == 0 && klass->GetClassLoader() == class_loader) { + classes_.erase(it); + return true; + } + } + for (It it = image_classes_.lower_bound(hash), end = classes_.end(); + it != end && it->first == hash; ++it) { + mirror::Class* klass = it->second; + kh.ChangeClass(klass); + if (strcmp(kh.GetDescriptor(), descriptor) == 0 && klass->GetClassLoader() == class_loader) { + image_classes_.erase(it); + return true; + } + } + return false; +} + +mirror::Class* ClassLinker::LookupClass(const char* descriptor, + const mirror::ClassLoader* class_loader) { + size_t hash = Hash(descriptor); + MutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_); + // TODO: determine if its better to search classes_ or image_classes_ first + mirror::Class* klass = NULL; + // Use image class only if the class_loader is null. + if (class_loader == NULL) { + klass = LookupClassLocked(descriptor, class_loader, hash, image_classes_); + } + if (klass != NULL) { + return klass; + } + return LookupClassLocked(descriptor, class_loader, hash, classes_); +} + +mirror::Class* ClassLinker::LookupClassLocked(const char* descriptor, + const mirror::ClassLoader* class_loader, + size_t hash, const Table& classes) { + ClassHelper kh(NULL, this); + typedef Table::const_iterator It; // TODO: C++0x auto + for (It it = classes.lower_bound(hash), end = classes_.end(); it != end && it->first == hash; ++it) { + mirror::Class* klass = it->second; + kh.ChangeClass(klass); + if (strcmp(descriptor, kh.GetDescriptor()) == 0 && klass->GetClassLoader() == class_loader) { +#ifndef NDEBUG + for (++it; it != end && it->first == hash; ++it) { + mirror::Class* klass2 = it->second; + kh.ChangeClass(klass2); + CHECK(!(strcmp(descriptor, kh.GetDescriptor()) == 0 && klass2->GetClassLoader() == class_loader)) + << PrettyClass(klass) << " " << klass << " " << klass->GetClassLoader() << " " + << PrettyClass(klass2) << " " << klass2 << " " << klass2->GetClassLoader(); + } +#endif + return klass; + } + } + return NULL; +} + +void ClassLinker::LookupClasses(const char* descriptor, std::vector& classes) { + classes.clear(); + size_t hash = Hash(descriptor); + MutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_); + typedef Table::const_iterator It; // TODO: C++0x auto + // TODO: determine if its better to search classes_ or image_classes_ first + ClassHelper kh(NULL, this); + for (It it = classes_.lower_bound(hash), end = classes_.end(); it != end && it->first == hash; ++it) { + mirror::Class* klass = it->second; + kh.ChangeClass(klass); + if (strcmp(descriptor, kh.GetDescriptor()) == 0) { + classes.push_back(klass); + } + } + for (It it = image_classes_.lower_bound(hash), end = classes_.end(); it != end && it->first == hash; ++it) { + mirror::Class* klass = it->second; + kh.ChangeClass(klass); + if (strcmp(descriptor, kh.GetDescriptor()) == 0) { + classes.push_back(klass); + } + } +} + +void ClassLinker::VerifyClass(mirror::Class* klass) { + // TODO: assert that the monitor on the Class is held + Thread* self = Thread::Current(); + ObjectLock lock(self, klass); + + // Don't attempt to re-verify if already sufficiently verified. + if (klass->IsVerified() || + (klass->IsCompileTimeVerified() && Runtime::Current()->IsCompiler())) { + return; + } + + // The class might already be erroneous, for example at compile time if we attempted to verify + // this class as a parent to another. + if (klass->IsErroneous()) { + ThrowEarlierClassFailure(klass); + return; + } + + if (klass->GetStatus() == mirror::Class::kStatusResolved) { + klass->SetStatus(mirror::Class::kStatusVerifying); + } else { + CHECK_EQ(klass->GetStatus(), mirror::Class::kStatusRetryVerificationAtRuntime) + << PrettyClass(klass); + CHECK(!Runtime::Current()->IsCompiler()); + klass->SetStatus(mirror::Class::kStatusVerifyingAtRuntime); + } + + // Verify super class. + mirror::Class* super = klass->GetSuperClass(); + if (super != NULL) { + // Acquire lock to prevent races on verifying the super class. + ObjectLock lock(self, super); + + if (!super->IsVerified() && !super->IsErroneous()) { + Runtime::Current()->GetClassLinker()->VerifyClass(super); + } + if (!super->IsCompileTimeVerified()) { + std::string error_msg(StringPrintf("Rejecting class %s that attempts to sub-class erroneous class %s", + PrettyDescriptor(klass).c_str(), + PrettyDescriptor(super).c_str())); + LOG(ERROR) << error_msg << " in " << klass->GetDexCache()->GetLocation()->ToModifiedUtf8(); + SirtRef cause(self, self->GetException(NULL)); + if (cause.get() != NULL) { + self->ClearException(); + } + ThrowVerifyError(klass, "%s", error_msg.c_str()); + if (cause.get() != NULL) { + self->GetException(NULL)->SetCause(cause.get()); + } + klass->SetStatus(mirror::Class::kStatusError); + return; + } + } + + // Try to use verification information from the oat file, otherwise do runtime verification. + const DexFile& dex_file = *klass->GetDexCache()->GetDexFile(); + mirror::Class::Status oat_file_class_status(mirror::Class::kStatusNotReady); + bool preverified = VerifyClassUsingOatFile(dex_file, klass, oat_file_class_status); + verifier::MethodVerifier::FailureKind verifier_failure = verifier::MethodVerifier::kNoFailure; + if (oat_file_class_status == mirror::Class::kStatusError) { + LOG(WARNING) << "Skipping runtime verification of erroneous class " << PrettyDescriptor(klass) + << " in " << klass->GetDexCache()->GetLocation()->ToModifiedUtf8(); + ThrowVerifyError(klass, "Rejecting class %s because it failed compile-time verification", + PrettyDescriptor(klass).c_str()); + klass->SetStatus(mirror::Class::kStatusError); + return; + } + std::string error_msg; + if (!preverified) { + verifier_failure = verifier::MethodVerifier::VerifyClass(klass, error_msg, Runtime::Current()->IsCompiler()); + } + if (preverified || verifier_failure != verifier::MethodVerifier::kHardFailure) { + if (!preverified && verifier_failure != verifier::MethodVerifier::kNoFailure) { + LOG(WARNING) << "Soft verification failure in class " << PrettyDescriptor(klass) + << " in " << klass->GetDexCache()->GetLocation()->ToModifiedUtf8() + << " because: " << error_msg; + } + self->AssertNoPendingException(); + // Make sure all classes referenced by catch blocks are resolved. + ResolveClassExceptionHandlerTypes(dex_file, klass); + if (verifier_failure == verifier::MethodVerifier::kNoFailure) { + klass->SetStatus(mirror::Class::kStatusVerified); + } else { + CHECK_EQ(verifier_failure, verifier::MethodVerifier::kSoftFailure); + // Soft failures at compile time should be retried at runtime. Soft + // failures at runtime will be handled by slow paths in the generated + // code. Set status accordingly. + if (Runtime::Current()->IsCompiler()) { + klass->SetStatus(mirror::Class::kStatusRetryVerificationAtRuntime); + } else { + klass->SetStatus(mirror::Class::kStatusVerified); + } + } + } else { + LOG(ERROR) << "Verification failed on class " << PrettyDescriptor(klass) + << " in " << klass->GetDexCache()->GetLocation()->ToModifiedUtf8() + << " because: " << error_msg; + self->AssertNoPendingException(); + ThrowVerifyError(klass, "%s", error_msg.c_str()); + klass->SetStatus(mirror::Class::kStatusError); + } +} + +bool ClassLinker::VerifyClassUsingOatFile(const DexFile& dex_file, mirror::Class* klass, + mirror::Class::Status& oat_file_class_status) { + if (!Runtime::Current()->IsStarted()) { + return false; + } + if (Runtime::Current()->UseCompileTimeClassPath()) { + return false; + } + const OatFile* oat_file = FindOpenedOatFileForDexFile(dex_file); + CHECK(oat_file != NULL) << dex_file.GetLocation() << " " << PrettyClass(klass); + const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_file.GetLocation()); + CHECK(oat_dex_file != NULL) << dex_file.GetLocation() << " " << PrettyClass(klass); + const char* descriptor = ClassHelper(klass).GetDescriptor(); + uint32_t class_def_index; + bool found = dex_file.FindClassDefIndex(descriptor, class_def_index); + CHECK(found) << dex_file.GetLocation() << " " << PrettyClass(klass) << " " << descriptor; + UniquePtr oat_class(oat_dex_file->GetOatClass(class_def_index)); + CHECK(oat_class.get() != NULL) + << dex_file.GetLocation() << " " << PrettyClass(klass) << " " << descriptor; + oat_file_class_status = oat_class->GetStatus(); + if (oat_file_class_status == mirror::Class::kStatusVerified || + oat_file_class_status == mirror::Class::kStatusInitialized) { + return true; + } + if (oat_file_class_status == mirror::Class::kStatusRetryVerificationAtRuntime) { + // Compile time verification failed with a soft error. Compile time verification can fail + // because we have incomplete type information. Consider the following: + // class ... { + // Foo x; + // .... () { + // if (...) { + // v1 gets assigned a type of resolved class Foo + // } else { + // v1 gets assigned a type of unresolved class Bar + // } + // iput x = v1 + // } } + // when we merge v1 following the if-the-else it results in Conflict + // (see verifier::RegType::Merge) as we can't know the type of Bar and we could possibly be + // allowing an unsafe assignment to the field x in the iput (javac may have compiled this as + // it knew Bar was a sub-class of Foo, but for us this may have been moved into a separate apk + // at compile time). + return false; + } + if (oat_file_class_status == mirror::Class::kStatusError) { + // Compile time verification failed with a hard error. This is caused by invalid instructions + // in the class. These errors are unrecoverable. + return false; + } + if (oat_file_class_status == mirror::Class::kStatusNotReady) { + // Status is uninitialized if we couldn't determine the status at compile time, for example, + // not loading the class. + // TODO: when the verifier doesn't rely on Class-es failing to resolve/load the type hierarchy + // isn't a problem and this case shouldn't occur + return false; + } + LOG(FATAL) << "Unexpected class status: " << oat_file_class_status + << " " << dex_file.GetLocation() << " " << PrettyClass(klass) << " " << descriptor; + + return false; +} + +void ClassLinker::ResolveClassExceptionHandlerTypes(const DexFile& dex_file, mirror::Class* klass) { + for (size_t i = 0; i < klass->NumDirectMethods(); i++) { + ResolveMethodExceptionHandlerTypes(dex_file, klass->GetDirectMethod(i)); + } + for (size_t i = 0; i < klass->NumVirtualMethods(); i++) { + ResolveMethodExceptionHandlerTypes(dex_file, klass->GetVirtualMethod(i)); + } +} + +void ClassLinker::ResolveMethodExceptionHandlerTypes(const DexFile& dex_file, + mirror::AbstractMethod* method) { + // similar to DexVerifier::ScanTryCatchBlocks and dex2oat's ResolveExceptionsForMethod. + const DexFile::CodeItem* code_item = dex_file.GetCodeItem(method->GetCodeItemOffset()); + if (code_item == NULL) { + return; // native or abstract method + } + if (code_item->tries_size_ == 0) { + return; // nothing to process + } + const byte* handlers_ptr = DexFile::GetCatchHandlerData(*code_item, 0); + uint32_t handlers_size = DecodeUnsignedLeb128(&handlers_ptr); + ClassLinker* linker = Runtime::Current()->GetClassLinker(); + for (uint32_t idx = 0; idx < handlers_size; idx++) { + CatchHandlerIterator iterator(handlers_ptr); + for (; iterator.HasNext(); iterator.Next()) { + // Ensure exception types are resolved so that they don't need resolution to be delivered, + // unresolved exception types will be ignored by exception delivery + if (iterator.GetHandlerTypeIndex() != DexFile::kDexNoIndex16) { + mirror::Class* exception_type = linker->ResolveType(iterator.GetHandlerTypeIndex(), method); + if (exception_type == NULL) { + DCHECK(Thread::Current()->IsExceptionPending()); + Thread::Current()->ClearException(); + } + } + } + handlers_ptr = iterator.EndDataPointer(); + } +} + +static void CheckProxyConstructor(mirror::AbstractMethod* constructor); +static void CheckProxyMethod(mirror::AbstractMethod* method, + SirtRef& prototype); + +mirror::Class* ClassLinker::CreateProxyClass(mirror::String* name, + mirror::ObjectArray* interfaces, + mirror::ClassLoader* loader, + mirror::ObjectArray* methods, + mirror::ObjectArray >* throws) { + Thread* self = Thread::Current(); + SirtRef klass(self, AllocClass(self, GetClassRoot(kJavaLangClass), + sizeof(mirror::SynthesizedProxyClass))); + CHECK(klass.get() != NULL); + DCHECK(klass->GetClass() != NULL); + klass->SetObjectSize(sizeof(mirror::Proxy)); + klass->SetAccessFlags(kAccClassIsProxy | kAccPublic | kAccFinal); + klass->SetClassLoader(loader); + DCHECK_EQ(klass->GetPrimitiveType(), Primitive::kPrimNot); + klass->SetName(name); + mirror::Class* proxy_class = GetClassRoot(kJavaLangReflectProxy); + klass->SetDexCache(proxy_class->GetDexCache()); + + klass->SetStatus(mirror::Class::kStatusIdx); + + klass->SetDexTypeIndex(DexFile::kDexNoIndex16); + + // Instance fields are inherited, but we add a couple of static fields... + klass->SetSFields(AllocFieldArray(self, 2)); + // 1. Create a static field 'interfaces' that holds the _declared_ interfaces implemented by + // our proxy, so Class.getInterfaces doesn't return the flattened set. + SirtRef interfaces_sfield(self, AllocField(self)); + klass->SetStaticField(0, interfaces_sfield.get()); + interfaces_sfield->SetDexFieldIndex(0); + interfaces_sfield->SetDeclaringClass(klass.get()); + interfaces_sfield->SetAccessFlags(kAccStatic | kAccPublic | kAccFinal); + // 2. Create a static field 'throws' that holds exceptions thrown by our methods. + SirtRef throws_sfield(self, AllocField(self)); + klass->SetStaticField(1, throws_sfield.get()); + throws_sfield->SetDexFieldIndex(1); + throws_sfield->SetDeclaringClass(klass.get()); + throws_sfield->SetAccessFlags(kAccStatic | kAccPublic | kAccFinal); + + // Proxies have 1 direct method, the constructor + klass->SetDirectMethods(AllocAbstractMethodArray(self, 1)); + klass->SetDirectMethod(0, CreateProxyConstructor(self, klass, proxy_class)); + + // Create virtual method using specified prototypes + size_t num_virtual_methods = methods->GetLength(); + klass->SetVirtualMethods(AllocMethodArray(self, num_virtual_methods)); + for (size_t i = 0; i < num_virtual_methods; ++i) { + SirtRef prototype(self, methods->Get(i)); + klass->SetVirtualMethod(i, CreateProxyMethod(self, klass, prototype)); + } + + klass->SetSuperClass(proxy_class); // The super class is java.lang.reflect.Proxy + klass->SetStatus(mirror::Class::kStatusLoaded); // Class is now effectively in the loaded state + self->AssertNoPendingException(); + + // Link the fields and virtual methods, creating vtable and iftables + if (!LinkClass(klass, interfaces)) { + klass->SetStatus(mirror::Class::kStatusError); + return NULL; + } + { + ObjectLock lock(self, klass.get()); // Must hold lock on object when initializing. + interfaces_sfield->SetObject(klass.get(), interfaces); + throws_sfield->SetObject(klass.get(), throws); + klass->SetStatus(mirror::Class::kStatusInitialized); + } + + // sanity checks + if (kIsDebugBuild) { + CHECK(klass->GetIFields() == NULL); + CheckProxyConstructor(klass->GetDirectMethod(0)); + for (size_t i = 0; i < num_virtual_methods; ++i) { + SirtRef prototype(self, methods->Get(i)); + CheckProxyMethod(klass->GetVirtualMethod(i), prototype); + } + + std::string interfaces_field_name(StringPrintf("java.lang.Class[] %s.interfaces", + name->ToModifiedUtf8().c_str())); + CHECK_EQ(PrettyField(klass->GetStaticField(0)), interfaces_field_name); + + std::string throws_field_name(StringPrintf("java.lang.Class[][] %s.throws", + name->ToModifiedUtf8().c_str())); + CHECK_EQ(PrettyField(klass->GetStaticField(1)), throws_field_name); + + mirror::SynthesizedProxyClass* synth_proxy_class = + down_cast(klass.get()); + CHECK_EQ(synth_proxy_class->GetInterfaces(), interfaces); + CHECK_EQ(synth_proxy_class->GetThrows(), throws); + } + return klass.get(); +} + +std::string ClassLinker::GetDescriptorForProxy(const mirror::Class* proxy_class) { + DCHECK(proxy_class->IsProxyClass()); + mirror::String* name = proxy_class->GetName(); + DCHECK(name != NULL); + return DotToDescriptor(name->ToModifiedUtf8().c_str()); +} + +mirror::AbstractMethod* ClassLinker::FindMethodForProxy(const mirror::Class* proxy_class, + const mirror::AbstractMethod* proxy_method) { + DCHECK(proxy_class->IsProxyClass()); + DCHECK(proxy_method->IsProxyMethod()); + // Locate the dex cache of the original interface/Object + mirror::DexCache* dex_cache = NULL; + { + mirror::ObjectArray* resolved_types = proxy_method->GetDexCacheResolvedTypes(); + MutexLock mu(Thread::Current(), dex_lock_); + for (size_t i = 0; i != dex_caches_.size(); ++i) { + if (dex_caches_[i]->GetResolvedTypes() == resolved_types) { + dex_cache = dex_caches_[i]; + break; + } + } + } + CHECK(dex_cache != NULL); + uint32_t method_idx = proxy_method->GetDexMethodIndex(); + mirror::AbstractMethod* resolved_method = dex_cache->GetResolvedMethod(method_idx); + CHECK(resolved_method != NULL); + return resolved_method; +} + + +mirror::AbstractMethod* ClassLinker::CreateProxyConstructor(Thread* self, + SirtRef& klass, + mirror::Class* proxy_class) { + // Create constructor for Proxy that must initialize h + mirror::ObjectArray* proxy_direct_methods = + proxy_class->GetDirectMethods(); + CHECK_EQ(proxy_direct_methods->GetLength(), 15); + mirror::AbstractMethod* proxy_constructor = proxy_direct_methods->Get(2); + // Clone the existing constructor of Proxy (our constructor would just invoke it so steal its + // code_ too) + mirror::AbstractMethod* constructor = + down_cast(proxy_constructor->Clone(self)); + // Make this constructor public and fix the class to be our Proxy version + constructor->SetAccessFlags((constructor->GetAccessFlags() & ~kAccProtected) | kAccPublic); + constructor->SetDeclaringClass(klass.get()); + return constructor; +} + +static void CheckProxyConstructor(mirror::AbstractMethod* constructor) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + CHECK(constructor->IsConstructor()); + MethodHelper mh(constructor); + CHECK_STREQ(mh.GetName(), ""); + CHECK_EQ(mh.GetSignature(), std::string("(Ljava/lang/reflect/InvocationHandler;)V")); + DCHECK(constructor->IsPublic()); +} + +mirror::AbstractMethod* ClassLinker::CreateProxyMethod(Thread* self, SirtRef& klass, + SirtRef& prototype) { + // Ensure prototype is in dex cache so that we can use the dex cache to look up the overridden + // prototype method + prototype->GetDeclaringClass()->GetDexCache()->SetResolvedMethod(prototype->GetDexMethodIndex(), + prototype.get()); + // We steal everything from the prototype (such as DexCache, invoke stub, etc.) then specialize + // as necessary + mirror::AbstractMethod* method = down_cast(prototype->Clone(self)); + + // Set class to be the concrete proxy class and clear the abstract flag, modify exceptions to + // the intersection of throw exceptions as defined in Proxy + method->SetDeclaringClass(klass.get()); + method->SetAccessFlags((method->GetAccessFlags() & ~kAccAbstract) | kAccFinal); + + // At runtime the method looks like a reference and argument saving method, clone the code + // related parameters from this method. + mirror::AbstractMethod* refs_and_args = + Runtime::Current()->GetCalleeSaveMethod(Runtime::kRefsAndArgs); + method->SetCoreSpillMask(refs_and_args->GetCoreSpillMask()); + method->SetFpSpillMask(refs_and_args->GetFpSpillMask()); + method->SetFrameSizeInBytes(refs_and_args->GetFrameSizeInBytes()); +#if !defined(ART_USE_PORTABLE_COMPILER) + method->SetEntryPointFromCompiledCode(reinterpret_cast(art_quick_proxy_invoke_handler)); +#else + method->SetEntryPointFromCompiledCode(reinterpret_cast(art_portable_proxy_invoke_handler)); +#endif + method->SetEntryPointFromInterpreter(artInterpreterToQuickEntry); + + return method; +} + +static void CheckProxyMethod(mirror::AbstractMethod* method, + SirtRef& prototype) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + // Basic sanity + CHECK(!prototype->IsFinal()); + CHECK(method->IsFinal()); + CHECK(!method->IsAbstract()); + + // The proxy method doesn't have its own dex cache or dex file and so it steals those of its + // interface prototype. The exception to this are Constructors and the Class of the Proxy itself. + CHECK_EQ(prototype->GetDexCacheStrings(), method->GetDexCacheStrings()); + CHECK_EQ(prototype->GetDexCacheResolvedMethods(), method->GetDexCacheResolvedMethods()); + CHECK_EQ(prototype->GetDexCacheResolvedTypes(), method->GetDexCacheResolvedTypes()); + CHECK_EQ(prototype->GetDexCacheInitializedStaticStorage(), + method->GetDexCacheInitializedStaticStorage()); + CHECK_EQ(prototype->GetDexMethodIndex(), method->GetDexMethodIndex()); + + MethodHelper mh(method); + MethodHelper mh2(prototype.get()); + CHECK_STREQ(mh.GetName(), mh2.GetName()); + CHECK_STREQ(mh.GetShorty(), mh2.GetShorty()); + // More complex sanity - via dex cache + CHECK_EQ(mh.GetReturnType(), mh2.GetReturnType()); +} + +bool ClassLinker::InitializeClass(mirror::Class* klass, bool can_run_clinit, bool can_init_statics) { + CHECK(klass->IsResolved() || klass->IsErroneous()) + << PrettyClass(klass) << ": state=" << klass->GetStatus(); + + Thread* self = Thread::Current(); + + mirror::AbstractMethod* clinit = NULL; + { + // see JLS 3rd edition, 12.4.2 "Detailed Initialization Procedure" for the locking protocol + ObjectLock lock(self, klass); + + if (klass->GetStatus() == mirror::Class::kStatusInitialized) { + return true; + } + + if (klass->IsErroneous()) { + ThrowEarlierClassFailure(klass); + return false; + } + + if (klass->GetStatus() == mirror::Class::kStatusResolved || + klass->GetStatus() == mirror::Class::kStatusRetryVerificationAtRuntime) { + VerifyClass(klass); + if (klass->GetStatus() != mirror::Class::kStatusVerified) { + if (klass->GetStatus() == mirror::Class::kStatusError) { + CHECK(self->IsExceptionPending()); + } + return false; + } + } + + clinit = klass->FindDeclaredDirectMethod("", "()V"); + if (clinit != NULL && !can_run_clinit) { + // if the class has a but we can't run it during compilation, + // don't bother going to kStatusInitializing. We return false so that + // sub-classes don't believe this class is initialized. + // Opportunistically link non-static methods, TODO: don't initialize and dirty pages + // in second pass. + return false; + } + + // If the class is kStatusInitializing, either this thread is + // initializing higher up the stack or another thread has beat us + // to initializing and we need to wait. Either way, this + // invocation of InitializeClass will not be responsible for + // running and will return. + if (klass->GetStatus() == mirror::Class::kStatusInitializing) { + // We caught somebody else in the act; was it us? + if (klass->GetClinitThreadId() == self->GetTid()) { + // Yes. That's fine. Return so we can continue initializing. + return true; + } + // No. That's fine. Wait for another thread to finish initializing. + return WaitForInitializeClass(klass, self, lock); + } + + if (!ValidateSuperClassDescriptors(klass)) { + klass->SetStatus(mirror::Class::kStatusError); + lock.NotifyAll(); + return false; + } + + DCHECK_EQ(klass->GetStatus(), mirror::Class::kStatusVerified) << PrettyClass(klass); + + klass->SetClinitThreadId(self->GetTid()); + klass->SetStatus(mirror::Class::kStatusInitializing); + } + + uint64_t t0 = NanoTime(); + + if (!InitializeSuperClass(klass, can_run_clinit, can_init_statics)) { + // Super class initialization failed, this can be because we can't run + // super-class class initializers in which case we'll be verified. + // Otherwise this class is erroneous. + if (!can_run_clinit) { + CHECK(klass->IsVerified()); + } else { + CHECK(klass->IsErroneous()); + } + // Signal to any waiting threads that saw this class as initializing. + ObjectLock lock(self, klass); + lock.NotifyAll(); + return false; + } + + bool has_static_field_initializers = InitializeStaticFields(klass); + + if (clinit != NULL) { + if (Runtime::Current()->IsStarted()) { + JValue result; + clinit->Invoke(self, NULL, 0, &result, 'V'); + } else { + art::interpreter::EnterInterpreterFromInvoke(self, clinit, NULL, NULL, NULL); + } + } + + FixupStaticTrampolines(klass); + + uint64_t t1 = NanoTime(); + + bool success = true; + { + ObjectLock lock(self, klass); + + if (self->IsExceptionPending()) { + WrapExceptionInInitializer(); + klass->SetStatus(mirror::Class::kStatusError); + success = false; + } else { + RuntimeStats* global_stats = Runtime::Current()->GetStats(); + RuntimeStats* thread_stats = self->GetStats(); + ++global_stats->class_init_count; + ++thread_stats->class_init_count; + global_stats->class_init_time_ns += (t1 - t0); + thread_stats->class_init_time_ns += (t1 - t0); + // Set the class as initialized except if we can't initialize static fields and static field + // initialization is necessary. + if (!can_init_statics && has_static_field_initializers) { + klass->SetStatus(mirror::Class::kStatusVerified); // Don't leave class in initializing state. + success = false; + } else { + klass->SetStatus(mirror::Class::kStatusInitialized); + } + if (VLOG_IS_ON(class_linker)) { + ClassHelper kh(klass); + LOG(INFO) << "Initialized class " << kh.GetDescriptor() << " from " << kh.GetLocation(); + } + } + lock.NotifyAll(); + } + return success; +} + +bool ClassLinker::WaitForInitializeClass(mirror::Class* klass, Thread* self, ObjectLock& lock) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + while (true) { + self->AssertNoPendingException(); + lock.WaitIgnoringInterrupts(); + + // When we wake up, repeat the test for init-in-progress. If + // there's an exception pending (only possible if + // "interruptShouldThrow" was set), bail out. + if (self->IsExceptionPending()) { + WrapExceptionInInitializer(); + klass->SetStatus(mirror::Class::kStatusError); + return false; + } + // Spurious wakeup? Go back to waiting. + if (klass->GetStatus() == mirror::Class::kStatusInitializing) { + continue; + } + if (klass->GetStatus() == mirror::Class::kStatusVerified && Runtime::Current()->IsCompiler()) { + // Compile time initialization failed. + return false; + } + if (klass->IsErroneous()) { + // The caller wants an exception, but it was thrown in a + // different thread. Synthesize one here. + ThrowNoClassDefFoundError(" failed for class %s; see exception in other thread", + PrettyDescriptor(klass).c_str()); + return false; + } + if (klass->IsInitialized()) { + return true; + } + LOG(FATAL) << "Unexpected class status. " << PrettyClass(klass) << " is " << klass->GetStatus(); + } + LOG(FATAL) << "Not Reached" << PrettyClass(klass); +} + +bool ClassLinker::ValidateSuperClassDescriptors(const mirror::Class* klass) { + if (klass->IsInterface()) { + return true; + } + // begin with the methods local to the superclass + if (klass->HasSuperClass() && + klass->GetClassLoader() != klass->GetSuperClass()->GetClassLoader()) { + const mirror::Class* super = klass->GetSuperClass(); + for (int i = super->GetVTable()->GetLength() - 1; i >= 0; --i) { + const mirror::AbstractMethod* method = klass->GetVTable()->Get(i); + if (method != super->GetVTable()->Get(i) && + !IsSameMethodSignatureInDifferentClassContexts(method, super, klass)) { + ThrowLinkageError(klass, "Class %s method %s resolves differently in superclass %s", + PrettyDescriptor(klass).c_str(), PrettyMethod(method).c_str(), + PrettyDescriptor(super).c_str()); + return false; + } + } + } + mirror::IfTable* iftable = klass->GetIfTable(); + for (int32_t i = 0; i < klass->GetIfTableCount(); ++i) { + mirror::Class* interface = iftable->GetInterface(i); + if (klass->GetClassLoader() != interface->GetClassLoader()) { + for (size_t j = 0; j < interface->NumVirtualMethods(); ++j) { + const mirror::AbstractMethod* method = iftable->GetMethodArray(i)->Get(j); + if (!IsSameMethodSignatureInDifferentClassContexts(method, interface, + method->GetDeclaringClass())) { + ThrowLinkageError(klass, "Class %s method %s resolves differently in interface %s", + PrettyDescriptor(method->GetDeclaringClass()).c_str(), + PrettyMethod(method).c_str(), + PrettyDescriptor(interface).c_str()); + return false; + } + } + } + } + return true; +} + +// Returns true if classes referenced by the signature of the method are the +// same classes in klass1 as they are in klass2. +bool ClassLinker::IsSameMethodSignatureInDifferentClassContexts(const mirror::AbstractMethod* method, + const mirror::Class* klass1, + const mirror::Class* klass2) { + if (klass1 == klass2) { + return true; + } + const DexFile& dex_file = *method->GetDeclaringClass()->GetDexCache()->GetDexFile(); + const DexFile::ProtoId& proto_id = + dex_file.GetMethodPrototype(dex_file.GetMethodId(method->GetDexMethodIndex())); + for (DexFileParameterIterator it(dex_file, proto_id); it.HasNext(); it.Next()) { + const char* descriptor = it.GetDescriptor(); + if (descriptor == NULL) { + break; + } + if (descriptor[0] == 'L' || descriptor[0] == '[') { + // Found a non-primitive type. + if (!IsSameDescriptorInDifferentClassContexts(descriptor, klass1, klass2)) { + return false; + } + } + } + // Check the return type + const char* descriptor = dex_file.GetReturnTypeDescriptor(proto_id); + if (descriptor[0] == 'L' || descriptor[0] == '[') { + if (!IsSameDescriptorInDifferentClassContexts(descriptor, klass1, klass2)) { + return false; + } + } + return true; +} + +// Returns true if the descriptor resolves to the same class in the context of klass1 and klass2. +bool ClassLinker::IsSameDescriptorInDifferentClassContexts(const char* descriptor, + const mirror::Class* klass1, + const mirror::Class* klass2) { + CHECK(descriptor != NULL); + CHECK(klass1 != NULL); + CHECK(klass2 != NULL); + if (klass1 == klass2) { + return true; + } + mirror::Class* found1 = FindClass(descriptor, klass1->GetClassLoader()); + if (found1 == NULL) { + Thread::Current()->ClearException(); + } + mirror::Class* found2 = FindClass(descriptor, klass2->GetClassLoader()); + if (found2 == NULL) { + Thread::Current()->ClearException(); + } + return found1 == found2; +} + +bool ClassLinker::InitializeSuperClass(mirror::Class* klass, bool can_run_clinit, bool can_init_fields) { + CHECK(klass != NULL); + if (!klass->IsInterface() && klass->HasSuperClass()) { + mirror::Class* super_class = klass->GetSuperClass(); + if (!super_class->IsInitialized()) { + CHECK(!super_class->IsInterface()); + // Must hold lock on object when initializing and setting status. + Thread* self = Thread::Current(); + ObjectLock lock(self, klass); + bool super_initialized = InitializeClass(super_class, can_run_clinit, can_init_fields); + // TODO: check for a pending exception + if (!super_initialized) { + if (!can_run_clinit) { + // Don't set status to error when we can't run . + CHECK_EQ(klass->GetStatus(), mirror::Class::kStatusInitializing) << PrettyClass(klass); + klass->SetStatus(mirror::Class::kStatusVerified); + return false; + } + klass->SetStatus(mirror::Class::kStatusError); + klass->NotifyAll(self); + return false; + } + } + } + return true; +} + +bool ClassLinker::EnsureInitialized(mirror::Class* c, bool can_run_clinit, bool can_init_fields) { + DCHECK(c != NULL); + if (c->IsInitialized()) { + return true; + } + + Thread* self = Thread::Current(); + ScopedThreadStateChange tsc(self, kRunnable); + bool success = InitializeClass(c, can_run_clinit, can_init_fields); + if (!success) { + CHECK(self->IsExceptionPending() || !can_run_clinit) << PrettyClass(c); + } + return success; +} + +void ClassLinker::ConstructFieldMap(const DexFile& dex_file, const DexFile::ClassDef& dex_class_def, + mirror::Class* c, SafeMap& field_map) { + mirror::ClassLoader* cl = c->GetClassLoader(); + const byte* class_data = dex_file.GetClassData(dex_class_def); + ClassDataItemIterator it(dex_file, class_data); + for (size_t i = 0; it.HasNextStaticField(); i++, it.Next()) { + field_map.Put(i, ResolveField(dex_file, it.GetMemberIndex(), c->GetDexCache(), cl, true)); + } +} + +bool ClassLinker::InitializeStaticFields(mirror::Class* klass) { + size_t num_static_fields = klass->NumStaticFields(); + if (num_static_fields == 0) { + return false; + } + mirror::DexCache* dex_cache = klass->GetDexCache(); + // TODO: this seems like the wrong check. do we really want !IsPrimitive && !IsArray? + if (dex_cache == NULL) { + return false; + } + ClassHelper kh(klass); + const DexFile::ClassDef* dex_class_def = kh.GetClassDef(); + CHECK(dex_class_def != NULL); + const DexFile& dex_file = kh.GetDexFile(); + EncodedStaticFieldValueIterator it(dex_file, dex_cache, klass->GetClassLoader(), + this, *dex_class_def); + + if (it.HasNext()) { + // We reordered the fields, so we need to be able to map the field indexes to the right fields. + SafeMap field_map; + ConstructFieldMap(dex_file, *dex_class_def, klass, field_map); + for (size_t i = 0; it.HasNext(); i++, it.Next()) { + it.ReadValueToField(field_map.Get(i)); + } + return true; + } + return false; +} + +bool ClassLinker::LinkClass(SirtRef& klass, + mirror::ObjectArray* interfaces) { + CHECK_EQ(mirror::Class::kStatusLoaded, klass->GetStatus()); + if (!LinkSuperClass(klass)) { + return false; + } + if (!LinkMethods(klass, interfaces)) { + return false; + } + if (!LinkInstanceFields(klass)) { + return false; + } + if (!LinkStaticFields(klass)) { + return false; + } + CreateReferenceInstanceOffsets(klass); + CreateReferenceStaticOffsets(klass); + CHECK_EQ(mirror::Class::kStatusLoaded, klass->GetStatus()); + klass->SetStatus(mirror::Class::kStatusResolved); + return true; +} + +bool ClassLinker::LoadSuperAndInterfaces(SirtRef& klass, const DexFile& dex_file) { + CHECK_EQ(mirror::Class::kStatusIdx, klass->GetStatus()); + StringPiece descriptor(dex_file.StringByTypeIdx(klass->GetDexTypeIndex())); + const DexFile::ClassDef* class_def = dex_file.FindClassDef(descriptor); + CHECK(class_def != NULL); + uint16_t super_class_idx = class_def->superclass_idx_; + if (super_class_idx != DexFile::kDexNoIndex16) { + mirror::Class* super_class = ResolveType(dex_file, super_class_idx, klass.get()); + if (super_class == NULL) { + DCHECK(Thread::Current()->IsExceptionPending()); + return false; + } + // Verify + if (!klass->CanAccess(super_class)) { + ThrowIllegalAccessError(klass.get(), "Class %s extended by class %s is inaccessible", + PrettyDescriptor(super_class).c_str(), + PrettyDescriptor(klass.get()).c_str()); + return false; + } + klass->SetSuperClass(super_class); + } + const DexFile::TypeList* interfaces = dex_file.GetInterfacesList(*class_def); + if (interfaces != NULL) { + for (size_t i = 0; i < interfaces->Size(); i++) { + uint16_t idx = interfaces->GetTypeItem(i).type_idx_; + mirror::Class* interface = ResolveType(dex_file, idx, klass.get()); + if (interface == NULL) { + DCHECK(Thread::Current()->IsExceptionPending()); + return false; + } + // Verify + if (!klass->CanAccess(interface)) { + // TODO: the RI seemed to ignore this in my testing. + ThrowIllegalAccessError(klass.get(), "Interface %s implemented by class %s is inaccessible", + PrettyDescriptor(interface).c_str(), + PrettyDescriptor(klass.get()).c_str()); + return false; + } + } + } + // Mark the class as loaded. + klass->SetStatus(mirror::Class::kStatusLoaded); + return true; +} + +bool ClassLinker::LinkSuperClass(SirtRef& klass) { + CHECK(!klass->IsPrimitive()); + mirror::Class* super = klass->GetSuperClass(); + if (klass.get() == GetClassRoot(kJavaLangObject)) { + if (super != NULL) { + ThrowClassFormatError(klass.get(), "java.lang.Object must not have a superclass"); + return false; + } + return true; + } + if (super == NULL) { + ThrowLinkageError(klass.get(), "No superclass defined for class %s", + PrettyDescriptor(klass.get()).c_str()); + return false; + } + // Verify + if (super->IsFinal() || super->IsInterface()) { + ThrowIncompatibleClassChangeError(klass.get(), "Superclass %s of %s is %s", + PrettyDescriptor(super).c_str(), + PrettyDescriptor(klass.get()).c_str(), + super->IsFinal() ? "declared final" : "an interface"); + return false; + } + if (!klass->CanAccess(super)) { + ThrowIllegalAccessError(klass.get(), "Superclass %s is inaccessible to class %s", + PrettyDescriptor(super).c_str(), + PrettyDescriptor(klass.get()).c_str()); + return false; + } + + // Inherit kAccClassIsFinalizable from the superclass in case this class doesn't override finalize. + if (super->IsFinalizable()) { + klass->SetFinalizable(); + } + + // Inherit reference flags (if any) from the superclass. + int reference_flags = (super->GetAccessFlags() & kAccReferenceFlagsMask); + if (reference_flags != 0) { + klass->SetAccessFlags(klass->GetAccessFlags() | reference_flags); + } + // Disallow custom direct subclasses of java.lang.ref.Reference. + if (init_done_ && super == GetClassRoot(kJavaLangRefReference)) { + ThrowLinkageError(klass.get(), + "Class %s attempts to subclass java.lang.ref.Reference, which is not allowed", + PrettyDescriptor(klass.get()).c_str()); + return false; + } + +#ifndef NDEBUG + // Ensure super classes are fully resolved prior to resolving fields.. + while (super != NULL) { + CHECK(super->IsResolved()); + super = super->GetSuperClass(); + } +#endif + return true; +} + +// Populate the class vtable and itable. Compute return type indices. +bool ClassLinker::LinkMethods(SirtRef& klass, + mirror::ObjectArray* interfaces) { + if (klass->IsInterface()) { + // No vtable. + size_t count = klass->NumVirtualMethods(); + if (!IsUint(16, count)) { + ThrowClassFormatError(klass.get(), "Too many methods on interface: %zd", count); + return false; + } + for (size_t i = 0; i < count; ++i) { + klass->GetVirtualMethodDuringLinking(i)->SetMethodIndex(i); + } + // Link interface method tables + return LinkInterfaceMethods(klass, interfaces); + } else { + // Link virtual and interface method tables + return LinkVirtualMethods(klass) && LinkInterfaceMethods(klass, interfaces); + } + return true; +} + +bool ClassLinker::LinkVirtualMethods(SirtRef& klass) { + Thread* self = Thread::Current(); + if (klass->HasSuperClass()) { + uint32_t max_count = klass->NumVirtualMethods() + klass->GetSuperClass()->GetVTable()->GetLength(); + size_t actual_count = klass->GetSuperClass()->GetVTable()->GetLength(); + CHECK_LE(actual_count, max_count); + // TODO: do not assign to the vtable field until it is fully constructed. + SirtRef > + vtable(self, klass->GetSuperClass()->GetVTable()->CopyOf(self, max_count)); + // See if any of our virtual methods override the superclass. + MethodHelper local_mh(NULL, this); + MethodHelper super_mh(NULL, this); + for (size_t i = 0; i < klass->NumVirtualMethods(); ++i) { + mirror::AbstractMethod* local_method = klass->GetVirtualMethodDuringLinking(i); + local_mh.ChangeMethod(local_method); + size_t j = 0; + for (; j < actual_count; ++j) { + mirror::AbstractMethod* super_method = vtable->Get(j); + super_mh.ChangeMethod(super_method); + if (local_mh.HasSameNameAndSignature(&super_mh)) { + if (klass->CanAccessMember(super_method->GetDeclaringClass(), super_method->GetAccessFlags())) { + if (super_method->IsFinal()) { + ThrowLinkageError(klass.get(), "Method %s overrides final method in class %s", + PrettyMethod(local_method).c_str(), + super_mh.GetDeclaringClassDescriptor()); + return false; + } + vtable->Set(j, local_method); + local_method->SetMethodIndex(j); + break; + } else { + LOG(WARNING) << "Before Android 4.1, method " << PrettyMethod(local_method) + << " would have incorrectly overridden the package-private method in " + << PrettyDescriptor(super_mh.GetDeclaringClassDescriptor()); + } + } + } + if (j == actual_count) { + // Not overriding, append. + vtable->Set(actual_count, local_method); + local_method->SetMethodIndex(actual_count); + actual_count += 1; + } + } + if (!IsUint(16, actual_count)) { + ThrowClassFormatError(klass.get(), "Too many methods defined on class: %zd", actual_count); + return false; + } + // Shrink vtable if possible + CHECK_LE(actual_count, max_count); + if (actual_count < max_count) { + vtable.reset(vtable->CopyOf(self, actual_count)); + } + klass->SetVTable(vtable.get()); + } else { + CHECK(klass.get() == GetClassRoot(kJavaLangObject)); + uint32_t num_virtual_methods = klass->NumVirtualMethods(); + if (!IsUint(16, num_virtual_methods)) { + ThrowClassFormatError(klass.get(), "Too many methods: %d", num_virtual_methods); + return false; + } + SirtRef > + vtable(self, AllocMethodArray(self, num_virtual_methods)); + for (size_t i = 0; i < num_virtual_methods; ++i) { + mirror::AbstractMethod* virtual_method = klass->GetVirtualMethodDuringLinking(i); + vtable->Set(i, virtual_method); + virtual_method->SetMethodIndex(i & 0xFFFF); + } + klass->SetVTable(vtable.get()); + } + return true; +} + +bool ClassLinker::LinkInterfaceMethods(SirtRef& klass, + mirror::ObjectArray* interfaces) { + size_t super_ifcount; + if (klass->HasSuperClass()) { + super_ifcount = klass->GetSuperClass()->GetIfTableCount(); + } else { + super_ifcount = 0; + } + size_t ifcount = super_ifcount; + ClassHelper kh(klass.get(), this); + uint32_t num_interfaces = interfaces == NULL ? kh.NumDirectInterfaces() : interfaces->GetLength(); + ifcount += num_interfaces; + for (size_t i = 0; i < num_interfaces; i++) { + mirror::Class* interface = interfaces == NULL ? kh.GetDirectInterface(i) : interfaces->Get(i); + ifcount += interface->GetIfTableCount(); + } + if (ifcount == 0) { + // Class implements no interfaces. + DCHECK_EQ(klass->GetIfTableCount(), 0); + DCHECK(klass->GetIfTable() == NULL); + return true; + } + if (ifcount == super_ifcount) { + // Class implements same interfaces as parent, are any of these not marker interfaces? + bool has_non_marker_interface = false; + mirror::IfTable* super_iftable = klass->GetSuperClass()->GetIfTable(); + for (size_t i = 0; i < ifcount; ++i) { + if (super_iftable->GetMethodArrayCount(i) > 0) { + has_non_marker_interface = true; + break; + } + } + if (!has_non_marker_interface) { + // Class just inherits marker interfaces from parent so recycle parent's iftable. + klass->SetIfTable(super_iftable); + return true; + } + } + Thread* self = Thread::Current(); + SirtRef iftable(self, AllocIfTable(self, ifcount)); + if (super_ifcount != 0) { + mirror::IfTable* super_iftable = klass->GetSuperClass()->GetIfTable(); + for (size_t i = 0; i < super_ifcount; i++) { + mirror::Class* super_interface = super_iftable->GetInterface(i); + iftable->SetInterface(i, super_interface); + } + } + // Flatten the interface inheritance hierarchy. + size_t idx = super_ifcount; + for (size_t i = 0; i < num_interfaces; i++) { + mirror::Class* interface = interfaces == NULL ? kh.GetDirectInterface(i) : interfaces->Get(i); + DCHECK(interface != NULL); + if (!interface->IsInterface()) { + ClassHelper ih(interface); + ThrowIncompatibleClassChangeError(klass.get(), "Class %s implements non-interface class %s", + PrettyDescriptor(klass.get()).c_str(), + PrettyDescriptor(ih.GetDescriptor()).c_str()); + return false; + } + // Check if interface is already in iftable + bool duplicate = false; + for (size_t j = 0; j < idx; j++) { + mirror::Class* existing_interface = iftable->GetInterface(j); + if (existing_interface == interface) { + duplicate = true; + break; + } + } + if (!duplicate) { + // Add this non-duplicate interface. + iftable->SetInterface(idx++, interface); + // Add this interface's non-duplicate super-interfaces. + for (int32_t j = 0; j < interface->GetIfTableCount(); j++) { + mirror::Class* super_interface = interface->GetIfTable()->GetInterface(j); + bool super_duplicate = false; + for (size_t k = 0; k < idx; k++) { + mirror::Class* existing_interface = iftable->GetInterface(k); + if (existing_interface == super_interface) { + super_duplicate = true; + break; + } + } + if (!super_duplicate) { + iftable->SetInterface(idx++, super_interface); + } + } + } + } + // Shrink iftable in case duplicates were found + if (idx < ifcount) { + iftable.reset(down_cast(iftable->CopyOf(self, idx * mirror::IfTable::kMax))); + ifcount = idx; + } else { + CHECK_EQ(idx, ifcount); + } + klass->SetIfTable(iftable.get()); + + // If we're an interface, we don't need the vtable pointers, so we're done. + if (klass->IsInterface()) { + return true; + } + std::vector miranda_list; + MethodHelper vtable_mh(NULL, this); + MethodHelper interface_mh(NULL, this); + for (size_t i = 0; i < ifcount; ++i) { + mirror::Class* interface = iftable->GetInterface(i); + size_t num_methods = interface->NumVirtualMethods(); + if (num_methods > 0) { + mirror::ObjectArray* method_array = + AllocMethodArray(self, num_methods); + iftable->SetMethodArray(i, method_array); + mirror::ObjectArray* vtable = klass->GetVTableDuringLinking(); + for (size_t j = 0; j < num_methods; ++j) { + mirror::AbstractMethod* interface_method = interface->GetVirtualMethod(j); + interface_mh.ChangeMethod(interface_method); + int32_t k; + // For each method listed in the interface's method list, find the + // matching method in our class's method list. We want to favor the + // subclass over the superclass, which just requires walking + // back from the end of the vtable. (This only matters if the + // superclass defines a private method and this class redefines + // it -- otherwise it would use the same vtable slot. In .dex files + // those don't end up in the virtual method table, so it shouldn't + // matter which direction we go. We walk it backward anyway.) + for (k = vtable->GetLength() - 1; k >= 0; --k) { + mirror::AbstractMethod* vtable_method = vtable->Get(k); + vtable_mh.ChangeMethod(vtable_method); + if (interface_mh.HasSameNameAndSignature(&vtable_mh)) { + if (!vtable_method->IsAbstract() && !vtable_method->IsPublic()) { + ThrowIllegalAccessError(klass.get(), + "Method '%s' implementing interface method '%s' is not public", + PrettyMethod(vtable_method).c_str(), + PrettyMethod(interface_method).c_str()); + return false; + } + method_array->Set(j, vtable_method); + break; + } + } + if (k < 0) { + SirtRef miranda_method(self, NULL); + for (size_t mir = 0; mir < miranda_list.size(); mir++) { + mirror::AbstractMethod* mir_method = miranda_list[mir]; + vtable_mh.ChangeMethod(mir_method); + if (interface_mh.HasSameNameAndSignature(&vtable_mh)) { + miranda_method.reset(miranda_list[mir]); + break; + } + } + if (miranda_method.get() == NULL) { + // point the interface table at a phantom slot + miranda_method.reset(down_cast(interface_method->Clone(self))); + miranda_list.push_back(miranda_method.get()); + } + method_array->Set(j, miranda_method.get()); + } + } + } + } + if (!miranda_list.empty()) { + int old_method_count = klass->NumVirtualMethods(); + int new_method_count = old_method_count + miranda_list.size(); + klass->SetVirtualMethods((old_method_count == 0) + ? AllocMethodArray(self, new_method_count) + : klass->GetVirtualMethods()->CopyOf(self, new_method_count)); + + SirtRef > + vtable(self, klass->GetVTableDuringLinking()); + CHECK(vtable.get() != NULL); + int old_vtable_count = vtable->GetLength(); + int new_vtable_count = old_vtable_count + miranda_list.size(); + vtable.reset(vtable->CopyOf(self, new_vtable_count)); + for (size_t i = 0; i < miranda_list.size(); ++i) { + mirror::AbstractMethod* method = miranda_list[i]; + // Leave the declaring class alone as type indices are relative to it + method->SetAccessFlags(method->GetAccessFlags() | kAccMiranda); + method->SetMethodIndex(0xFFFF & (old_vtable_count + i)); + klass->SetVirtualMethod(old_method_count + i, method); + vtable->Set(old_vtable_count + i, method); + } + // TODO: do not assign to the vtable field until it is fully constructed. + klass->SetVTable(vtable.get()); + } + + mirror::ObjectArray* vtable = klass->GetVTableDuringLinking(); + for (int i = 0; i < vtable->GetLength(); ++i) { + CHECK(vtable->Get(i) != NULL); + } + +// klass->DumpClass(std::cerr, Class::kDumpClassFullDetail); + + return true; +} + +bool ClassLinker::LinkInstanceFields(SirtRef& klass) { + CHECK(klass.get() != NULL); + return LinkFields(klass, false); +} + +bool ClassLinker::LinkStaticFields(SirtRef& klass) { + CHECK(klass.get() != NULL); + size_t allocated_class_size = klass->GetClassSize(); + bool success = LinkFields(klass, true); + CHECK_EQ(allocated_class_size, klass->GetClassSize()); + return success; +} + +struct LinkFieldsComparator { + explicit LinkFieldsComparator(FieldHelper* fh) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) + : fh_(fh) {} + // No thread safety analysis as will be called from STL. Checked lock held in constructor. + bool operator()(const mirror::Field* field1, const mirror::Field* field2) + NO_THREAD_SAFETY_ANALYSIS { + // First come reference fields, then 64-bit, and finally 32-bit + fh_->ChangeField(field1); + Primitive::Type type1 = fh_->GetTypeAsPrimitiveType(); + fh_->ChangeField(field2); + Primitive::Type type2 = fh_->GetTypeAsPrimitiveType(); + bool isPrimitive1 = type1 != Primitive::kPrimNot; + bool isPrimitive2 = type2 != Primitive::kPrimNot; + bool is64bit1 = isPrimitive1 && (type1 == Primitive::kPrimLong || type1 == Primitive::kPrimDouble); + bool is64bit2 = isPrimitive2 && (type2 == Primitive::kPrimLong || type2 == Primitive::kPrimDouble); + int order1 = (!isPrimitive1 ? 0 : (is64bit1 ? 1 : 2)); + int order2 = (!isPrimitive2 ? 0 : (is64bit2 ? 1 : 2)); + if (order1 != order2) { + return order1 < order2; + } + + // same basic group? then sort by string. + fh_->ChangeField(field1); + StringPiece name1(fh_->GetName()); + fh_->ChangeField(field2); + StringPiece name2(fh_->GetName()); + return name1 < name2; + } + + FieldHelper* fh_; +}; + +bool ClassLinker::LinkFields(SirtRef& klass, bool is_static) { + size_t num_fields = + is_static ? klass->NumStaticFields() : klass->NumInstanceFields(); + + mirror::ObjectArray* fields = + is_static ? klass->GetSFields() : klass->GetIFields(); + + // Initialize size and field_offset + size_t size; + MemberOffset field_offset(0); + if (is_static) { + size = klass->GetClassSize(); + field_offset = mirror::Class::FieldsOffset(); + } else { + mirror::Class* super_class = klass->GetSuperClass(); + if (super_class != NULL) { + CHECK(super_class->IsResolved()); + field_offset = MemberOffset(super_class->GetObjectSize()); + } + size = field_offset.Uint32Value(); + } + + CHECK_EQ(num_fields == 0, fields == NULL); + + // we want a relatively stable order so that adding new fields + // minimizes disruption of C++ version such as Class and Method. + std::deque grouped_and_sorted_fields; + for (size_t i = 0; i < num_fields; i++) { + grouped_and_sorted_fields.push_back(fields->Get(i)); + } + FieldHelper fh(NULL, this); + std::sort(grouped_and_sorted_fields.begin(), + grouped_and_sorted_fields.end(), + LinkFieldsComparator(&fh)); + + // References should be at the front. + size_t current_field = 0; + size_t num_reference_fields = 0; + for (; current_field < num_fields; current_field++) { + mirror::Field* field = grouped_and_sorted_fields.front(); + fh.ChangeField(field); + Primitive::Type type = fh.GetTypeAsPrimitiveType(); + bool isPrimitive = type != Primitive::kPrimNot; + if (isPrimitive) { + break; // past last reference, move on to the next phase + } + grouped_and_sorted_fields.pop_front(); + num_reference_fields++; + fields->Set(current_field, field); + field->SetOffset(field_offset); + field_offset = MemberOffset(field_offset.Uint32Value() + sizeof(uint32_t)); + } + + // Now we want to pack all of the double-wide fields together. If + // we're not aligned, though, we want to shuffle one 32-bit field + // into place. If we can't find one, we'll have to pad it. + if (current_field != num_fields && !IsAligned<8>(field_offset.Uint32Value())) { + for (size_t i = 0; i < grouped_and_sorted_fields.size(); i++) { + mirror::Field* field = grouped_and_sorted_fields[i]; + fh.ChangeField(field); + Primitive::Type type = fh.GetTypeAsPrimitiveType(); + CHECK(type != Primitive::kPrimNot); // should only be working on primitive types + if (type == Primitive::kPrimLong || type == Primitive::kPrimDouble) { + continue; + } + fields->Set(current_field++, field); + field->SetOffset(field_offset); + // drop the consumed field + grouped_and_sorted_fields.erase(grouped_and_sorted_fields.begin() + i); + break; + } + // whether we found a 32-bit field for padding or not, we advance + field_offset = MemberOffset(field_offset.Uint32Value() + sizeof(uint32_t)); + } + + // Alignment is good, shuffle any double-wide fields forward, and + // finish assigning field offsets to all fields. + DCHECK(current_field == num_fields || IsAligned<8>(field_offset.Uint32Value())); + while (!grouped_and_sorted_fields.empty()) { + mirror::Field* field = grouped_and_sorted_fields.front(); + grouped_and_sorted_fields.pop_front(); + fh.ChangeField(field); + Primitive::Type type = fh.GetTypeAsPrimitiveType(); + CHECK(type != Primitive::kPrimNot); // should only be working on primitive types + fields->Set(current_field, field); + field->SetOffset(field_offset); + field_offset = MemberOffset(field_offset.Uint32Value() + + ((type == Primitive::kPrimLong || type == Primitive::kPrimDouble) + ? sizeof(uint64_t) + : sizeof(uint32_t))); + current_field++; + } + + // We lie to the GC about the java.lang.ref.Reference.referent field, so it doesn't scan it. + if (!is_static && + StringPiece(ClassHelper(klass.get(), this).GetDescriptor()) == "Ljava/lang/ref/Reference;") { + // We know there are no non-reference fields in the Reference classes, and we know + // that 'referent' is alphabetically last, so this is easy... + CHECK_EQ(num_reference_fields, num_fields); + fh.ChangeField(fields->Get(num_fields - 1)); + CHECK_STREQ(fh.GetName(), "referent"); + --num_reference_fields; + } + +#ifndef NDEBUG + // Make sure that all reference fields appear before + // non-reference fields, and all double-wide fields are aligned. + bool seen_non_ref = false; + for (size_t i = 0; i < num_fields; i++) { + mirror::Field* field = fields->Get(i); + if (false) { // enable to debug field layout + LOG(INFO) << "LinkFields: " << (is_static ? "static" : "instance") + << " class=" << PrettyClass(klass.get()) + << " field=" << PrettyField(field) + << " offset=" << field->GetField32(MemberOffset(mirror::Field::OffsetOffset()), + false); + } + fh.ChangeField(field); + Primitive::Type type = fh.GetTypeAsPrimitiveType(); + bool is_primitive = type != Primitive::kPrimNot; + if (StringPiece(ClassHelper(klass.get(), this).GetDescriptor()) == "Ljava/lang/ref/Reference;" && + StringPiece(fh.GetName()) == "referent") { + is_primitive = true; // We lied above, so we have to expect a lie here. + } + if (is_primitive) { + if (!seen_non_ref) { + seen_non_ref = true; + DCHECK_EQ(num_reference_fields, i); + } + } else { + DCHECK(!seen_non_ref); + } + } + if (!seen_non_ref) { + DCHECK_EQ(num_fields, num_reference_fields); + } +#endif + size = field_offset.Uint32Value(); + // Update klass + if (is_static) { + klass->SetNumReferenceStaticFields(num_reference_fields); + klass->SetClassSize(size); + } else { + klass->SetNumReferenceInstanceFields(num_reference_fields); + if (!klass->IsVariableSize()) { + klass->SetObjectSize(size); + } + } + return true; +} + +// Set the bitmap of reference offsets, refOffsets, from the ifields +// list. +void ClassLinker::CreateReferenceInstanceOffsets(SirtRef& klass) { + uint32_t reference_offsets = 0; + mirror::Class* super_class = klass->GetSuperClass(); + if (super_class != NULL) { + reference_offsets = super_class->GetReferenceInstanceOffsets(); + // If our superclass overflowed, we don't stand a chance. + if (reference_offsets == CLASS_WALK_SUPER) { + klass->SetReferenceInstanceOffsets(reference_offsets); + return; + } + } + CreateReferenceOffsets(klass, false, reference_offsets); +} + +void ClassLinker::CreateReferenceStaticOffsets(SirtRef& klass) { + CreateReferenceOffsets(klass, true, 0); +} + +void ClassLinker::CreateReferenceOffsets(SirtRef& klass, bool is_static, + uint32_t reference_offsets) { + size_t num_reference_fields = + is_static ? klass->NumReferenceStaticFieldsDuringLinking() + : klass->NumReferenceInstanceFieldsDuringLinking(); + const mirror::ObjectArray* fields = + is_static ? klass->GetSFields() : klass->GetIFields(); + // All of the fields that contain object references are guaranteed + // to be at the beginning of the fields list. + for (size_t i = 0; i < num_reference_fields; ++i) { + // Note that byte_offset is the offset from the beginning of + // object, not the offset into instance data + const mirror::Field* field = fields->Get(i); + MemberOffset byte_offset = field->GetOffsetDuringLinking(); + CHECK_EQ(byte_offset.Uint32Value() & (CLASS_OFFSET_ALIGNMENT - 1), 0U); + if (CLASS_CAN_ENCODE_OFFSET(byte_offset.Uint32Value())) { + uint32_t new_bit = CLASS_BIT_FROM_OFFSET(byte_offset.Uint32Value()); + CHECK_NE(new_bit, 0U); + reference_offsets |= new_bit; + } else { + reference_offsets = CLASS_WALK_SUPER; + break; + } + } + // Update fields in klass + if (is_static) { + klass->SetReferenceStaticOffsets(reference_offsets); + } else { + klass->SetReferenceInstanceOffsets(reference_offsets); + } +} + +mirror::String* ClassLinker::ResolveString(const DexFile& dex_file, + uint32_t string_idx, mirror::DexCache* dex_cache) { + DCHECK(dex_cache != NULL); + mirror::String* resolved = dex_cache->GetResolvedString(string_idx); + if (resolved != NULL) { + return resolved; + } + const DexFile::StringId& string_id = dex_file.GetStringId(string_idx); + int32_t utf16_length = dex_file.GetStringLength(string_id); + const char* utf8_data = dex_file.GetStringData(string_id); + mirror::String* string = intern_table_->InternStrong(utf16_length, utf8_data); + dex_cache->SetResolvedString(string_idx, string); + return string; +} + +mirror::Class* ClassLinker::ResolveType(const DexFile& dex_file, + uint16_t type_idx, + mirror::DexCache* dex_cache, + mirror::ClassLoader* class_loader) { + DCHECK(dex_cache != NULL); + mirror::Class* resolved = dex_cache->GetResolvedType(type_idx); + if (resolved == NULL) { + const char* descriptor = dex_file.StringByTypeIdx(type_idx); + resolved = FindClass(descriptor, class_loader); + if (resolved != NULL) { + // TODO: we used to throw here if resolved's class loader was not the + // boot class loader. This was to permit different classes with the + // same name to be loaded simultaneously by different loaders + dex_cache->SetResolvedType(type_idx, resolved); + } else { + Thread* self = Thread::Current(); + CHECK(self->IsExceptionPending()) + << "Expected pending exception for failed resolution of: " << descriptor; + // Convert a ClassNotFoundException to a NoClassDefFoundError. + SirtRef cause(self, self->GetException(NULL)); + if (cause->InstanceOf(GetClassRoot(kJavaLangClassNotFoundException))) { + Thread::Current()->ClearException(); + ThrowNoClassDefFoundError("Failed resolution of: %s", descriptor); + self->GetException(NULL)->SetCause(cause.get()); + } + } + } + return resolved; +} + +mirror::AbstractMethod* ClassLinker::ResolveMethod(const DexFile& dex_file, + uint32_t method_idx, + mirror::DexCache* dex_cache, + mirror::ClassLoader* class_loader, + const mirror::AbstractMethod* referrer, + InvokeType type) { + DCHECK(dex_cache != NULL); + // Check for hit in the dex cache. + mirror::AbstractMethod* resolved = dex_cache->GetResolvedMethod(method_idx); + if (resolved != NULL) { + return resolved; + } + // Fail, get the declaring class. + const DexFile::MethodId& method_id = dex_file.GetMethodId(method_idx); + mirror::Class* klass = ResolveType(dex_file, method_id.class_idx_, dex_cache, class_loader); + if (klass == NULL) { + DCHECK(Thread::Current()->IsExceptionPending()); + return NULL; + } + // Scan using method_idx, this saves string compares but will only hit for matching dex + // caches/files. + switch (type) { + case kDirect: // Fall-through. + case kStatic: + resolved = klass->FindDirectMethod(dex_cache, method_idx); + break; + case kInterface: + resolved = klass->FindInterfaceMethod(dex_cache, method_idx); + DCHECK(resolved == NULL || resolved->GetDeclaringClass()->IsInterface()); + break; + case kSuper: // Fall-through. + case kVirtual: + resolved = klass->FindVirtualMethod(dex_cache, method_idx); + break; + default: + LOG(FATAL) << "Unreachable - invocation type: " << type; + } + if (resolved == NULL) { + // Search by name, which works across dex files. + const char* name = dex_file.StringDataByIdx(method_id.name_idx_); + std::string signature(dex_file.CreateMethodSignature(method_id.proto_idx_, NULL)); + switch (type) { + case kDirect: // Fall-through. + case kStatic: + resolved = klass->FindDirectMethod(name, signature); + break; + case kInterface: + resolved = klass->FindInterfaceMethod(name, signature); + DCHECK(resolved == NULL || resolved->GetDeclaringClass()->IsInterface()); + break; + case kSuper: // Fall-through. + case kVirtual: + resolved = klass->FindVirtualMethod(name, signature); + break; + } + } + if (resolved != NULL) { + // We found a method, check for incompatible class changes. + if (resolved->CheckIncompatibleClassChange(type)) { + resolved = NULL; + } + } + if (resolved != NULL) { + // Be a good citizen and update the dex cache to speed subsequent calls. + dex_cache->SetResolvedMethod(method_idx, resolved); + return resolved; + } else { + // We failed to find the method which means either an access error, an incompatible class + // change, or no such method. First try to find the method among direct and virtual methods. + const char* name = dex_file.StringDataByIdx(method_id.name_idx_); + std::string signature(dex_file.CreateMethodSignature(method_id.proto_idx_, NULL)); + switch (type) { + case kDirect: + case kStatic: + resolved = klass->FindVirtualMethod(name, signature); + break; + case kInterface: + case kVirtual: + case kSuper: + resolved = klass->FindDirectMethod(name, signature); + break; + } + + // If we found something, check that it can be accessed by the referrer. + if (resolved != NULL && referrer != NULL) { + mirror::Class* methods_class = resolved->GetDeclaringClass(); + mirror::Class* referring_class = referrer->GetDeclaringClass(); + if (!referring_class->CanAccess(methods_class)) { + ThrowIllegalAccessErrorClassForMethodDispatch(referring_class, methods_class, + referrer, resolved, type); + return NULL; + } else if (!referring_class->CanAccessMember(methods_class, + resolved->GetAccessFlags())) { + ThrowIllegalAccessErrorMethod(referring_class, resolved); + return NULL; + } + } + + // Otherwise, throw an IncompatibleClassChangeError if we found something, and check interface + // methods and throw if we find the method there. If we find nothing, throw a NoSuchMethodError. + switch (type) { + case kDirect: + case kStatic: + if (resolved != NULL) { + ThrowIncompatibleClassChangeError(type, kVirtual, resolved, referrer); + } else { + resolved = klass->FindInterfaceMethod(name, signature); + if (resolved != NULL) { + ThrowIncompatibleClassChangeError(type, kInterface, resolved, referrer); + } else { + ThrowNoSuchMethodError(type, klass, name, signature); + } + } + break; + case kInterface: + if (resolved != NULL) { + ThrowIncompatibleClassChangeError(type, kDirect, resolved, referrer); + } else { + resolved = klass->FindVirtualMethod(name, signature); + if (resolved != NULL) { + ThrowIncompatibleClassChangeError(type, kVirtual, resolved, referrer); + } else { + ThrowNoSuchMethodError(type, klass, name, signature); + } + } + break; + case kSuper: + ThrowNoSuchMethodError(type, klass, name, signature); + break; + case kVirtual: + if (resolved != NULL) { + ThrowIncompatibleClassChangeError(type, kDirect, resolved, referrer); + } else { + resolved = klass->FindInterfaceMethod(name, signature); + if (resolved != NULL) { + ThrowIncompatibleClassChangeError(type, kInterface, resolved, referrer); + } else { + ThrowNoSuchMethodError(type, klass, name, signature); + } + } + break; + } + DCHECK(Thread::Current()->IsExceptionPending()); + return NULL; + } +} + +mirror::Field* ClassLinker::ResolveField(const DexFile& dex_file, + uint32_t field_idx, + mirror::DexCache* dex_cache, + mirror::ClassLoader* class_loader, + bool is_static) { + DCHECK(dex_cache != NULL); + mirror::Field* resolved = dex_cache->GetResolvedField(field_idx); + if (resolved != NULL) { + return resolved; + } + const DexFile::FieldId& field_id = dex_file.GetFieldId(field_idx); + mirror::Class* klass = ResolveType(dex_file, field_id.class_idx_, dex_cache, class_loader); + if (klass == NULL) { + DCHECK(Thread::Current()->IsExceptionPending()); + return NULL; + } + + if (is_static) { + resolved = klass->FindStaticField(dex_cache, field_idx); + } else { + resolved = klass->FindInstanceField(dex_cache, field_idx); + } + + if (resolved == NULL) { + const char* name = dex_file.GetFieldName(field_id); + const char* type = dex_file.GetFieldTypeDescriptor(field_id); + if (is_static) { + resolved = klass->FindStaticField(name, type); + } else { + resolved = klass->FindInstanceField(name, type); + } + if (resolved == NULL) { + ThrowNoSuchFieldError(is_static ? "static " : "instance ", klass, type, name); + return NULL; + } + } + dex_cache->SetResolvedField(field_idx, resolved); + return resolved; +} + +mirror::Field* ClassLinker::ResolveFieldJLS(const DexFile& dex_file, + uint32_t field_idx, + mirror::DexCache* dex_cache, + mirror::ClassLoader* class_loader) { + DCHECK(dex_cache != NULL); + mirror::Field* resolved = dex_cache->GetResolvedField(field_idx); + if (resolved != NULL) { + return resolved; + } + const DexFile::FieldId& field_id = dex_file.GetFieldId(field_idx); + mirror::Class* klass = ResolveType(dex_file, field_id.class_idx_, dex_cache, class_loader); + if (klass == NULL) { + DCHECK(Thread::Current()->IsExceptionPending()); + return NULL; + } + + const char* name = dex_file.GetFieldName(field_id); + const char* type = dex_file.GetFieldTypeDescriptor(field_id); + resolved = klass->FindField(name, type); + if (resolved != NULL) { + dex_cache->SetResolvedField(field_idx, resolved); + } else { + ThrowNoSuchFieldError("", klass, type, name); + } + return resolved; +} + +const char* ClassLinker::MethodShorty(uint32_t method_idx, mirror::AbstractMethod* referrer, + uint32_t* length) { + mirror::Class* declaring_class = referrer->GetDeclaringClass(); + mirror::DexCache* dex_cache = declaring_class->GetDexCache(); + const DexFile& dex_file = *dex_cache->GetDexFile(); + const DexFile::MethodId& method_id = dex_file.GetMethodId(method_idx); + return dex_file.GetMethodShorty(method_id, length); +} + +void ClassLinker::DumpAllClasses(int flags) const { + // TODO: at the time this was written, it wasn't safe to call PrettyField with the ClassLinker + // lock held, because it might need to resolve a field's type, which would try to take the lock. + std::vector all_classes; + { + MutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_); + typedef Table::const_iterator It; // TODO: C++0x auto + for (It it = classes_.begin(), end = classes_.end(); it != end; ++it) { + all_classes.push_back(it->second); + } + for (It it = image_classes_.begin(), end = image_classes_.end(); it != end; ++it) { + all_classes.push_back(it->second); + } + } + + for (size_t i = 0; i < all_classes.size(); ++i) { + all_classes[i]->DumpClass(std::cerr, flags); + } +} + +void ClassLinker::DumpForSigQuit(std::ostream& os) const { + MutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_); + os << "Loaded classes: " << image_classes_.size() << " image classes; " + << classes_.size() << " allocated classes\n"; +} + +size_t ClassLinker::NumLoadedClasses() const { + MutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_); + return classes_.size() + image_classes_.size(); +} + +pid_t ClassLinker::GetClassesLockOwner() { + return Locks::classlinker_classes_lock_->GetExclusiveOwnerTid(); +} + +pid_t ClassLinker::GetDexLockOwner() { + return dex_lock_.GetExclusiveOwnerTid(); +} + +void ClassLinker::SetClassRoot(ClassRoot class_root, mirror::Class* klass) { + DCHECK(!init_done_); + + DCHECK(klass != NULL); + DCHECK(klass->GetClassLoader() == NULL); + + DCHECK(class_roots_ != NULL); + DCHECK(class_roots_->Get(class_root) == NULL); + class_roots_->Set(class_root, klass); +} + +} // namespace art diff --git a/src/class_linker.h b/src/class_linker.h new file mode 100644 index 0000000000000000000000000000000000000000..d41373c7d7b6f12cc1990af8e07a60167a55a8c4 --- /dev/null +++ b/src/class_linker.h @@ -0,0 +1,610 @@ +/* + * Copyright (C) 2011 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_SRC_CLASS_LINKER_H_ +#define ART_SRC_CLASS_LINKER_H_ + +#include +#include +#include + +#include "base/macros.h" +#include "base/mutex.h" +#include "dex_file.h" +#include "gtest/gtest.h" +#include "root_visitor.h" +#include "oat_file.h" + +namespace art { +namespace mirror { + class ClassLoader; + class DexCache; + class DexCacheTest_Open_Test; + class IfTable; + template class ObjectArray; + class StackTraceElement; +} // namespace mirror +class ImageSpace; +class InternTable; +class ObjectLock; +template class SirtRef; + +typedef bool (ClassVisitor)(mirror::Class* c, void* arg); + +class ClassLinker { + public: + // Creates the class linker by bootstrapping from dex files. + static ClassLinker* CreateFromCompiler(const std::vector& boot_class_path, + InternTable* intern_table) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Creates the class linker from an image. + static ClassLinker* CreateFromImage(InternTable* intern_table) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + ~ClassLinker(); + + // Finds a class by its descriptor, loading it if necessary. + // If class_loader is null, searches boot_class_path_. + mirror::Class* FindClass(const char* descriptor, mirror::ClassLoader* class_loader) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::Class* FindSystemClass(const char* descriptor) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Define a new a class based on a ClassDef from a DexFile + mirror::Class* DefineClass(const StringPiece& descriptor, mirror::ClassLoader* class_loader, + const DexFile& dex_file, const DexFile::ClassDef& dex_class_def) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Finds a class by its descriptor, returning NULL if it isn't wasn't loaded + // by the given 'class_loader'. + mirror::Class* LookupClass(const char* descriptor, const mirror::ClassLoader* class_loader) + LOCKS_EXCLUDED(Locks::classlinker_classes_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Finds all the classes with the given descriptor, regardless of ClassLoader. + void LookupClasses(const char* descriptor, std::vector& classes) + LOCKS_EXCLUDED(Locks::classlinker_classes_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::Class* FindPrimitiveClass(char type) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // General class unloading is not supported, this is used to prune + // unwanted classes during image writing. + bool RemoveClass(const char* descriptor, const mirror::ClassLoader* class_loader) + LOCKS_EXCLUDED(Locks::classlinker_classes_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void DumpAllClasses(int flags) const + LOCKS_EXCLUDED(Locks::classlinker_classes_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void DumpForSigQuit(std::ostream& os) const + LOCKS_EXCLUDED(Locks::classlinker_classes_lock_); + + size_t NumLoadedClasses() const LOCKS_EXCLUDED(Locks::classlinker_classes_lock_); + + // Resolve a String with the given index from the DexFile, storing the + // result in the DexCache. The referrer is used to identify the + // target DexCache and ClassLoader to use for resolution. + mirror::String* ResolveString(uint32_t string_idx, const mirror::AbstractMethod* referrer) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Resolve a String with the given index from the DexFile, storing the + // result in the DexCache. + mirror::String* ResolveString(const DexFile& dex_file, uint32_t string_idx, + mirror::DexCache* dex_cache) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Resolve a Type with the given index from the DexFile, storing the + // result in the DexCache. The referrer is used to identity the + // target DexCache and ClassLoader to use for resolution. + mirror::Class* ResolveType(const DexFile& dex_file, uint16_t type_idx, + const mirror::Class* referrer) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + return ResolveType(dex_file, + type_idx, + referrer->GetDexCache(), + referrer->GetClassLoader()); + } + + // Resolve a Type with the given index from the DexFile, storing the + // result in the DexCache. The referrer is used to identify the + // target DexCache and ClassLoader to use for resolution. + mirror::Class* ResolveType(uint16_t type_idx, const mirror::AbstractMethod* referrer) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::Class* ResolveType(uint16_t type_idx, const mirror::Field* referrer) + SHARED_LOCKS_REQUIRED(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 + // type, since it may be referenced from but not contained within + // the given DexFile. + mirror::Class* ResolveType(const DexFile& dex_file, + uint16_t type_idx, + mirror::DexCache* dex_cache, + mirror::ClassLoader* class_loader) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Resolve a method with a given ID from the DexFile, storing the + // result in DexCache. The ClassLinker and ClassLoader are used as + // in ResolveType. What is unique is the method type argument which + // is used to determine if this method is a direct, static, or + // virtual method. + mirror::AbstractMethod* ResolveMethod(const DexFile& dex_file, + uint32_t method_idx, + mirror::DexCache* dex_cache, + mirror::ClassLoader* class_loader, + const mirror::AbstractMethod* referrer, + InvokeType type) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::AbstractMethod* ResolveMethod(uint32_t method_idx, const mirror::AbstractMethod* referrer, + InvokeType type) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::Field* ResolveField(uint32_t field_idx, const mirror::AbstractMethod* referrer, + bool is_static) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Resolve a field with a given ID from the DexFile, storing the + // result in DexCache. The ClassLinker and ClassLoader are used as + // in ResolveType. What is unique is the is_static argument which is + // used to determine if we are resolving a static or non-static + // field. + mirror::Field* ResolveField(const DexFile& dex_file, + uint32_t field_idx, + mirror::DexCache* dex_cache, + mirror::ClassLoader* class_loader, + bool is_static) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Resolve a field with a given ID from the DexFile, storing the + // result in DexCache. The ClassLinker and ClassLoader are used as + // in ResolveType. No is_static argument is provided so that Java + // field resolution semantics are followed. + mirror::Field* ResolveFieldJLS(const DexFile& dex_file, + uint32_t field_idx, + mirror::DexCache* dex_cache, + mirror::ClassLoader* class_loader) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Get shorty from method index without resolution. Used to do handlerization. + const char* MethodShorty(uint32_t method_idx, mirror::AbstractMethod* referrer, uint32_t* length) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Returns true on success, false if there's an exception pending. + // can_run_clinit=false allows the compiler to attempt to init a class, + // given the restriction that no execution is possible. + bool EnsureInitialized(mirror::Class* c, bool can_run_clinit, bool can_init_fields) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Initializes classes that have instances in the image but that have + // methods so they could not be initialized by the compiler. + void RunRootClinits() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void RegisterDexFile(const DexFile& dex_file) + LOCKS_EXCLUDED(dex_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + void RegisterDexFile(const DexFile& dex_file, SirtRef& dex_cache) + LOCKS_EXCLUDED(dex_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void RegisterOatFile(const OatFile& oat_file) + LOCKS_EXCLUDED(dex_lock_); + + const std::vector& GetBootClassPath() { + return boot_class_path_; + } + + void VisitClasses(ClassVisitor* visitor, void* arg) const + LOCKS_EXCLUDED(Locks::classlinker_classes_lock_); + // Less efficient variant of VisitClasses that doesn't hold the classlinker_classes_lock_ + // when calling the visitor. + void VisitClassesWithoutClassesLock(ClassVisitor* visitor, void* arg) const + LOCKS_EXCLUDED(Locks::classlinker_classes_lock_); + + void VisitRoots(RootVisitor* visitor, void* arg) + LOCKS_EXCLUDED(Locks::classlinker_classes_lock_, dex_lock_); + + mirror::DexCache* FindDexCache(const DexFile& dex_file) const + LOCKS_EXCLUDED(dex_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + bool IsDexFileRegistered(const DexFile& dex_file) const + LOCKS_EXCLUDED(dex_lock_); + void FixupDexCaches(mirror::AbstractMethod* resolution_method) const + LOCKS_EXCLUDED(dex_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Generate an oat file from a dex file + bool GenerateOatFile(const std::string& dex_filename, + int oat_fd, + const std::string& oat_cache_filename); + + const OatFile* FindOatFileFromOatLocation(const std::string& location) + LOCKS_EXCLUDED(dex_lock_); + + const OatFile* FindOatFileFromOatLocationLocked(const std::string& location) + EXCLUSIVE_LOCKS_REQUIRED(dex_lock_); + + // Finds the oat file for a dex location, generating the oat file if + // it is missing or out of date. Returns the DexFile from within the + // created oat file. + const DexFile* FindOrCreateOatFileForDexLocation(const std::string& dex_location, + const std::string& oat_location) + LOCKS_EXCLUDED(dex_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + const DexFile* FindOrCreateOatFileForDexLocationLocked(const std::string& dex_location, + const std::string& oat_location) + EXCLUSIVE_LOCKS_REQUIRED(dex_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + // Find a DexFile within an OatFile given a DexFile location. Note + // that this returns null if the location checksum of the DexFile + // does not match the OatFile. + const DexFile* FindDexFileInOatFileFromDexLocation(const std::string& location) + LOCKS_EXCLUDED(dex_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + + // Returns true if oat file contains the dex file with the given location and checksum. + static bool VerifyOatFileChecksums(const OatFile* oat_file, + const std::string& dex_location, + uint32_t dex_location_checksum) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // TODO: replace this with multiple methods that allocate the correct managed type. + template + mirror::ObjectArray* AllocObjectArray(Thread* self, size_t length) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::ObjectArray* AllocClassArray(Thread* self, size_t length) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::ObjectArray* AllocStringArray(Thread* self, size_t length) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::ObjectArray* AllocAbstractMethodArray(Thread* self, size_t length) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::ObjectArray* AllocMethodArray(Thread* self, size_t length) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::IfTable* AllocIfTable(Thread* self, size_t ifcount) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::ObjectArray* AllocFieldArray(Thread* self, size_t length) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::ObjectArray* AllocStackTraceElementArray(Thread* self, + size_t length) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void VerifyClass(mirror::Class* klass) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + bool VerifyClassUsingOatFile(const DexFile& dex_file, mirror::Class* klass, + mirror::Class::Status& oat_file_class_status) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + void ResolveClassExceptionHandlerTypes(const DexFile& dex_file, mirror::Class* klass) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + void ResolveMethodExceptionHandlerTypes(const DexFile& dex_file, mirror::AbstractMethod* klass) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::Class* CreateProxyClass(mirror::String* name, mirror::ObjectArray* interfaces, + mirror::ClassLoader* loader, + mirror::ObjectArray* methods, + mirror::ObjectArray >* throws) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + std::string GetDescriptorForProxy(const mirror::Class* proxy_class) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + mirror::AbstractMethod* FindMethodForProxy(const mirror::Class* proxy_class, + const mirror::AbstractMethod* proxy_method) + LOCKS_EXCLUDED(dex_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Get the oat code for a method when its class isn't yet initialized + const void* GetOatCodeFor(const mirror::AbstractMethod* method) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Get the oat code for a method from a method index. + const void* GetOatCodeFor(const DexFile& dex_file, uint32_t method_idx) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + pid_t GetClassesLockOwner(); // For SignalCatcher. + pid_t GetDexLockOwner(); // For SignalCatcher. + + bool IsDirty() const { + return is_dirty_; + } + + void Dirty() { + is_dirty_ = true; + } + + private: + explicit ClassLinker(InternTable*); + + const OatFile::OatMethod GetOatMethodFor(const mirror::AbstractMethod* method) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Initialize class linker by bootstraping from dex files + void InitFromCompiler(const std::vector& boot_class_path) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Initialize class linker from one or more images. + void InitFromImage() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + OatFile* OpenOat(const ImageSpace* space) + LOCKS_EXCLUDED(dex_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + static void InitFromImageCallback(mirror::Object* obj, void* arg) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void FinishInit() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // For early bootstrapping by Init + mirror::Class* AllocClass(Thread* self, mirror::Class* java_lang_Class, size_t class_size) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Alloc* convenience functions to avoid needing to pass in mirror::Class* + // values that are known to the ClassLinker such as + // kObjectArrayClass and kJavaLangString etc. + mirror::Class* AllocClass(Thread* self, size_t class_size) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + mirror::DexCache* AllocDexCache(Thread* self, const DexFile& dex_file) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + mirror::Field* AllocField(Thread* self) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + mirror::Method* AllocMethod(Thread* self) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + mirror::Constructor* AllocConstructor(Thread* self) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::Class* CreatePrimitiveClass(Thread* self, Primitive::Type type) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + mirror::Class* InitializePrimitiveClass(mirror::Class* primitive_class, Primitive::Type type) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + + mirror::Class* CreateArrayClass(const std::string& descriptor, mirror::ClassLoader* class_loader) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void AppendToBootClassPath(const DexFile& dex_file) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + void AppendToBootClassPath(const DexFile& dex_file, SirtRef& dex_cache) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void ConstructFieldMap(const DexFile& dex_file, const DexFile::ClassDef& dex_class_def, + mirror::Class* c, SafeMap& field_map) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + size_t SizeOfClass(const DexFile& dex_file, + const DexFile::ClassDef& dex_class_def); + + void LoadClass(const DexFile& dex_file, + const DexFile::ClassDef& dex_class_def, + SirtRef& klass, + mirror::ClassLoader* class_loader) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void LoadField(const DexFile& dex_file, const ClassDataItemIterator& it, + SirtRef& klass, SirtRef& dst) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::AbstractMethod* LoadMethod(Thread* self, const DexFile& dex_file, + const ClassDataItemIterator& dex_method, + SirtRef& klass) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void FixupStaticTrampolines(mirror::Class* klass) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Finds the associated oat class for a dex_file and descriptor + const OatFile::OatClass* GetOatClass(const DexFile& dex_file, const char* descriptor) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Attempts to insert a class into a class table. Returns NULL if + // the class was inserted, otherwise returns an existing class with + // the same descriptor and ClassLoader. + mirror::Class* InsertClass(const StringPiece& descriptor, mirror::Class* klass, bool image_class) + LOCKS_EXCLUDED(Locks::classlinker_classes_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void RegisterDexFileLocked(const DexFile& dex_file, SirtRef& dex_cache) + EXCLUSIVE_LOCKS_REQUIRED(dex_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + bool IsDexFileRegisteredLocked(const DexFile& dex_file) const EXCLUSIVE_LOCKS_REQUIRED(dex_lock_); + void RegisterOatFileLocked(const OatFile& oat_file) EXCLUSIVE_LOCKS_REQUIRED(dex_lock_) + EXCLUSIVE_LOCKS_REQUIRED(dex_lock_); + + bool InitializeClass(mirror::Class* klass, bool can_run_clinit, bool can_init_statics) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + bool WaitForInitializeClass(mirror::Class* klass, Thread* self, ObjectLock& lock); + bool ValidateSuperClassDescriptors(const mirror::Class* klass) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + bool InitializeSuperClass(mirror::Class* klass, bool can_run_clinit, bool can_init_fields) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + // Initialize static fields, returns true if fields were initialized. + bool InitializeStaticFields(mirror::Class* klass) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + bool IsSameDescriptorInDifferentClassContexts(const char* descriptor, + const mirror::Class* klass1, + const mirror::Class* klass2) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + bool IsSameMethodSignatureInDifferentClassContexts(const mirror::AbstractMethod* method, + const mirror::Class* klass1, + const mirror::Class* klass2) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + bool LinkClass(SirtRef& klass, mirror::ObjectArray* interfaces) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + bool LinkSuperClass(SirtRef& klass) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + bool LoadSuperAndInterfaces(SirtRef& klass, const DexFile& dex_file) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + bool LinkMethods(SirtRef& klass, mirror::ObjectArray* interfaces) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + bool LinkVirtualMethods(SirtRef& klass) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + bool LinkInterfaceMethods(SirtRef& klass, + mirror::ObjectArray* interfaces) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + bool LinkStaticFields(SirtRef& klass) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + bool LinkInstanceFields(SirtRef& klass) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + bool LinkFields(SirtRef& klass, bool is_static) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + + void CreateReferenceInstanceOffsets(SirtRef& klass) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + void CreateReferenceStaticOffsets(SirtRef& klass) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + void CreateReferenceOffsets(SirtRef& klass, bool is_static, + uint32_t reference_offsets) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // For use by ImageWriter to find DexCaches for its roots + const std::vector& GetDexCaches() { + return dex_caches_; + } + + const OatFile* FindOpenedOatFileForDexFile(const DexFile& dex_file) + LOCKS_EXCLUDED(dex_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + const OatFile* FindOpenedOatFileFromDexLocation(const std::string& dex_location) + EXCLUSIVE_LOCKS_REQUIRED(dex_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + const OatFile* FindOpenedOatFileFromOatLocation(const std::string& oat_location) + EXCLUSIVE_LOCKS_REQUIRED(dex_lock_); + const DexFile* VerifyAndOpenDexFileFromOatFile(const OatFile* oat_file, + const std::string& dex_location, + uint32_t dex_location_checksum) + EXCLUSIVE_LOCKS_REQUIRED(dex_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::AbstractMethod* CreateProxyConstructor(Thread* self, SirtRef& klass, + mirror::Class* proxy_class) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + mirror::AbstractMethod* CreateProxyMethod(Thread* self, SirtRef& klass, + SirtRef& prototype) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + std::vector boot_class_path_; + + mutable Mutex dex_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; + std::vector dex_caches_ GUARDED_BY(dex_lock_); + std::vector oat_files_ GUARDED_BY(dex_lock_); + + + // multimap from a string hash code of a class descriptor to + // mirror::Class* instances. Results should be compared for a matching + // Class::descriptor_ and Class::class_loader_. + typedef std::multimap Table; + Table image_classes_ GUARDED_BY(Locks::classlinker_classes_lock_); + Table classes_ GUARDED_BY(Locks::classlinker_classes_lock_); + + mirror::Class* LookupClassLocked(const char* descriptor, const mirror::ClassLoader* class_loader, + size_t hash, const Table& classes) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) + EXCLUSIVE_LOCKS_REQUIRED(Locks::classlinker_classes_lock_); + + // indexes into class_roots_. + // needs to be kept in sync with class_roots_descriptors_. + enum ClassRoot { + kJavaLangClass, + kJavaLangObject, + kClassArrayClass, + kObjectArrayClass, + kJavaLangString, + kJavaLangDexCache, + kJavaLangRefReference, + kJavaLangReflectConstructor, + kJavaLangReflectField, + kJavaLangReflectAbstractMethod, + kJavaLangReflectMethod, + kJavaLangReflectProxy, + kJavaLangStringArrayClass, + kJavaLangReflectAbstractMethodArrayClass, + kJavaLangReflectFieldArrayClass, + kJavaLangReflectMethodArrayClass, + kJavaLangClassLoader, + kJavaLangThrowable, + kJavaLangClassNotFoundException, + kJavaLangStackTraceElement, + kPrimitiveBoolean, + kPrimitiveByte, + kPrimitiveChar, + kPrimitiveDouble, + kPrimitiveFloat, + kPrimitiveInt, + kPrimitiveLong, + kPrimitiveShort, + kPrimitiveVoid, + kBooleanArrayClass, + kByteArrayClass, + kCharArrayClass, + kDoubleArrayClass, + kFloatArrayClass, + kIntArrayClass, + kLongArrayClass, + kShortArrayClass, + kJavaLangStackTraceElementArrayClass, + kClassRootsMax, + }; + mirror::ObjectArray* class_roots_; + + mirror::Class* GetClassRoot(ClassRoot class_root) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void SetClassRoot(ClassRoot class_root, mirror::Class* klass) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::ObjectArray* GetClassRoots() { + DCHECK(class_roots_ != NULL); + return class_roots_; + } + + static const char* class_roots_descriptors_[]; + + const char* GetClassRootDescriptor(ClassRoot class_root) { + const char* descriptor = class_roots_descriptors_[class_root]; + CHECK(descriptor != NULL); + return descriptor; + } + + mirror::IfTable* array_iftable_; + + bool init_done_; + bool is_dirty_; + + InternTable* intern_table_; + + friend class CommonTest; + friend class ImageWriter; // for GetClassRoots + friend class ObjectTest; + FRIEND_TEST(ClassLinkerTest, ClassRootDescriptors); + FRIEND_TEST(mirror::DexCacheTest, Open); + FRIEND_TEST(ExceptionTest, FindExceptionHandler); + FRIEND_TEST(ObjectTest, AllocObjectArray); + DISALLOW_COPY_AND_ASSIGN(ClassLinker); +}; + +} // namespace art + +#endif // ART_SRC_CLASS_LINKER_H_ diff --git a/src/class_linker_test.cc b/src/class_linker_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..73bdc613ab0c7696f63322d49b89af97a84755ea --- /dev/null +++ b/src/class_linker_test.cc @@ -0,0 +1,1078 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "class_linker.h" + +#include + +#include "UniquePtr.h" +#include "class_linker-inl.h" +#include "common_test.h" +#include "dex_file.h" +#include "heap.h" +#include "mirror/class-inl.h" +#include "mirror/dex_cache.h" +#include "mirror/field-inl.h" +#include "mirror/abstract_method.h" +#include "mirror/abstract_method-inl.h" +#include "mirror/object-inl.h" +#include "mirror/object_array-inl.h" +#include "mirror/proxy.h" +#include "mirror/stack_trace_element.h" +#include "runtime_support.h" +#include "sirt_ref.h" + +using namespace art::mirror; + +namespace art { + +class ClassLinkerTest : public CommonTest { + protected: + void AssertNonExistentClass(const std::string& descriptor) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + EXPECT_TRUE(class_linker_->FindSystemClass(descriptor.c_str()) == NULL); + Thread* self = Thread::Current(); + EXPECT_TRUE(self->IsExceptionPending()); + Object* exception = self->GetException(NULL); + self->ClearException(); + Class* exception_class = class_linker_->FindSystemClass("Ljava/lang/NoClassDefFoundError;"); + EXPECT_TRUE(exception->InstanceOf(exception_class)); + } + + void AssertPrimitiveClass(const std::string& descriptor) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + AssertPrimitiveClass(descriptor, class_linker_->FindSystemClass(descriptor.c_str())); + } + + void AssertPrimitiveClass(const std::string& descriptor, const Class* primitive) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + ClassHelper primitive_ch(primitive); + ASSERT_TRUE(primitive != NULL); + ASSERT_TRUE(primitive->GetClass() != NULL); + ASSERT_EQ(primitive->GetClass(), primitive->GetClass()->GetClass()); + EXPECT_TRUE(primitive->GetClass()->GetSuperClass() != NULL); + ASSERT_STREQ(descriptor.c_str(), primitive_ch.GetDescriptor()); + EXPECT_TRUE(primitive->GetSuperClass() == NULL); + EXPECT_FALSE(primitive->HasSuperClass()); + EXPECT_TRUE(primitive->GetClassLoader() == NULL); + EXPECT_EQ(Class::kStatusInitialized, primitive->GetStatus()); + EXPECT_FALSE(primitive->IsErroneous()); + EXPECT_TRUE(primitive->IsLoaded()); + EXPECT_TRUE(primitive->IsResolved()); + EXPECT_TRUE(primitive->IsVerified()); + EXPECT_TRUE(primitive->IsInitialized()); + EXPECT_FALSE(primitive->IsArrayInstance()); + EXPECT_FALSE(primitive->IsArrayClass()); + EXPECT_TRUE(primitive->GetComponentType() == NULL); + EXPECT_FALSE(primitive->IsInterface()); + EXPECT_TRUE(primitive->IsPublic()); + EXPECT_TRUE(primitive->IsFinal()); + EXPECT_TRUE(primitive->IsPrimitive()); + EXPECT_FALSE(primitive->IsSynthetic()); + EXPECT_EQ(0U, primitive->NumDirectMethods()); + EXPECT_EQ(0U, primitive->NumVirtualMethods()); + EXPECT_EQ(0U, primitive->NumInstanceFields()); + EXPECT_EQ(0U, primitive->NumStaticFields()); + EXPECT_EQ(0U, primitive_ch.NumDirectInterfaces()); + EXPECT_TRUE(primitive->GetVTable() == NULL); + EXPECT_EQ(0, primitive->GetIfTableCount()); + EXPECT_TRUE(primitive->GetIfTable() == NULL); + } + + void AssertArrayClass(const std::string& array_descriptor, + const std::string& component_type, + ClassLoader* class_loader) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + Class* array = class_linker_->FindClass(array_descriptor.c_str(), class_loader); + ClassHelper array_component_ch(array->GetComponentType()); + EXPECT_STREQ(component_type.c_str(), array_component_ch.GetDescriptor()); + EXPECT_EQ(class_loader, array->GetClassLoader()); + AssertArrayClass(array_descriptor, array); + } + + void AssertArrayClass(const std::string& array_descriptor, Class* array) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + ClassHelper kh(array); + ASSERT_TRUE(array != NULL); + ASSERT_TRUE(array->GetClass() != NULL); + ASSERT_EQ(array->GetClass(), array->GetClass()->GetClass()); + EXPECT_TRUE(array->GetClass()->GetSuperClass() != NULL); + ASSERT_STREQ(array_descriptor.c_str(), kh.GetDescriptor()); + EXPECT_TRUE(array->GetSuperClass() != NULL); + EXPECT_EQ(class_linker_->FindSystemClass("Ljava/lang/Object;"), array->GetSuperClass()); + EXPECT_TRUE(array->HasSuperClass()); + ASSERT_TRUE(array->GetComponentType() != NULL); + kh.ChangeClass(array->GetComponentType()); + ASSERT_TRUE(kh.GetDescriptor() != NULL); + EXPECT_EQ(Class::kStatusInitialized, array->GetStatus()); + EXPECT_FALSE(array->IsErroneous()); + EXPECT_TRUE(array->IsLoaded()); + EXPECT_TRUE(array->IsResolved()); + EXPECT_TRUE(array->IsVerified()); + EXPECT_TRUE(array->IsInitialized()); + EXPECT_FALSE(array->IsArrayInstance()); + EXPECT_TRUE(array->IsArrayClass()); + EXPECT_FALSE(array->IsInterface()); + EXPECT_EQ(array->GetComponentType()->IsPublic(), array->IsPublic()); + EXPECT_TRUE(array->IsFinal()); + EXPECT_FALSE(array->IsPrimitive()); + EXPECT_FALSE(array->IsSynthetic()); + EXPECT_EQ(0U, array->NumDirectMethods()); + EXPECT_EQ(0U, array->NumVirtualMethods()); + EXPECT_EQ(0U, array->NumInstanceFields()); + EXPECT_EQ(0U, array->NumStaticFields()); + kh.ChangeClass(array); + EXPECT_EQ(2U, kh.NumDirectInterfaces()); + EXPECT_TRUE(array->GetVTable() != NULL); + EXPECT_EQ(2, array->GetIfTableCount()); + IfTable* iftable = array->GetIfTable(); + ASSERT_TRUE(iftable != NULL); + kh.ChangeClass(kh.GetDirectInterface(0)); + EXPECT_STREQ(kh.GetDescriptor(), "Ljava/lang/Cloneable;"); + kh.ChangeClass(array); + kh.ChangeClass(kh.GetDirectInterface(1)); + EXPECT_STREQ(kh.GetDescriptor(), "Ljava/io/Serializable;"); + } + + void AssertMethod(AbstractMethod* method) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + MethodHelper mh(method); + EXPECT_TRUE(method != NULL); + EXPECT_TRUE(method->GetClass() != NULL); + EXPECT_TRUE(mh.GetName() != NULL); + EXPECT_TRUE(mh.GetSignature() != NULL); + + EXPECT_TRUE(method->GetDexCacheStrings() != NULL); + EXPECT_TRUE(method->GetDexCacheResolvedMethods() != NULL); + EXPECT_TRUE(method->GetDexCacheResolvedTypes() != NULL); + EXPECT_TRUE(method->GetDexCacheInitializedStaticStorage() != NULL); + EXPECT_EQ(method->GetDeclaringClass()->GetDexCache()->GetStrings(), + method->GetDexCacheStrings()); + EXPECT_EQ(method->GetDeclaringClass()->GetDexCache()->GetResolvedMethods(), + method->GetDexCacheResolvedMethods()); + EXPECT_EQ(method->GetDeclaringClass()->GetDexCache()->GetResolvedTypes(), + method->GetDexCacheResolvedTypes()); + EXPECT_EQ(method->GetDeclaringClass()->GetDexCache()->GetInitializedStaticStorage(), + method->GetDexCacheInitializedStaticStorage()); + } + + void AssertField(Class* klass, Field* field) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + FieldHelper fh(field); + EXPECT_TRUE(field != NULL); + EXPECT_TRUE(field->GetClass() != NULL); + EXPECT_EQ(klass, field->GetDeclaringClass()); + EXPECT_TRUE(fh.GetName() != NULL); + EXPECT_TRUE(fh.GetType() != NULL); + } + + void AssertClass(const std::string& descriptor, Class* klass) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + ClassHelper kh(klass); + EXPECT_STREQ(descriptor.c_str(), kh.GetDescriptor()); + if (descriptor == "Ljava/lang/Object;") { + EXPECT_FALSE(klass->HasSuperClass()); + } else { + EXPECT_TRUE(klass->HasSuperClass()); + EXPECT_TRUE(klass->GetSuperClass() != NULL); + } + EXPECT_TRUE(klass->GetClass() != NULL); + EXPECT_EQ(klass->GetClass(), klass->GetClass()->GetClass()); + EXPECT_TRUE(klass->GetDexCache() != NULL); + EXPECT_TRUE(klass->IsLoaded()); + EXPECT_TRUE(klass->IsResolved()); + EXPECT_FALSE(klass->IsErroneous()); + EXPECT_FALSE(klass->IsArrayClass()); + EXPECT_TRUE(klass->GetComponentType() == NULL); + EXPECT_TRUE(klass->IsInSamePackage(klass)); + EXPECT_TRUE(Class::IsInSamePackage(kh.GetDescriptor(), kh.GetDescriptor())); + if (klass->IsInterface()) { + EXPECT_TRUE(klass->IsAbstract()); + if (klass->NumDirectMethods() == 1) { + MethodHelper mh(klass->GetDirectMethod(0)); + EXPECT_TRUE(mh.IsClassInitializer()); + EXPECT_TRUE(klass->GetDirectMethod(0)->IsDirect()); + } else { + EXPECT_EQ(0U, klass->NumDirectMethods()); + } + } else { + if (!klass->IsSynthetic()) { + EXPECT_NE(0U, klass->NumDirectMethods()); + } + } + EXPECT_EQ(klass->IsInterface(), klass->GetVTable() == NULL); + const IfTable* iftable = klass->GetIfTable(); + for (int i = 0; i < klass->GetIfTableCount(); i++) { + Class* interface = iftable->GetInterface(i); + ASSERT_TRUE(interface != NULL); + if (klass->IsInterface()) { + EXPECT_EQ(0U, iftable->GetMethodArrayCount(i)); + } else { + EXPECT_EQ(interface->NumVirtualMethods(), iftable->GetMethodArrayCount(i)); + } + } + if (klass->IsAbstract()) { + EXPECT_FALSE(klass->IsFinal()); + } else { + EXPECT_FALSE(klass->IsAnnotation()); + } + if (klass->IsFinal()) { + EXPECT_FALSE(klass->IsAbstract()); + EXPECT_FALSE(klass->IsAnnotation()); + } + if (klass->IsAnnotation()) { + EXPECT_FALSE(klass->IsFinal()); + EXPECT_TRUE(klass->IsAbstract()); + } + + EXPECT_FALSE(klass->IsPrimitive()); + EXPECT_TRUE(klass->CanAccess(klass)); + + for (size_t i = 0; i < klass->NumDirectMethods(); i++) { + AbstractMethod* method = klass->GetDirectMethod(i); + AssertMethod(method); + EXPECT_TRUE(method->IsDirect()); + EXPECT_EQ(klass, method->GetDeclaringClass()); + } + + for (size_t i = 0; i < klass->NumVirtualMethods(); i++) { + AbstractMethod* method = klass->GetVirtualMethod(i); + AssertMethod(method); + EXPECT_FALSE(method->IsDirect()); + EXPECT_TRUE(method->GetDeclaringClass()->IsAssignableFrom(klass)); + } + + for (size_t i = 0; i < klass->NumInstanceFields(); i++) { + Field* field = klass->GetInstanceField(i); + AssertField(klass, field); + EXPECT_FALSE(field->IsStatic()); + } + + for (size_t i = 0; i < klass->NumStaticFields(); i++) { + Field* field = klass->GetStaticField(i); + AssertField(klass, field); + EXPECT_TRUE(field->IsStatic()); + } + + // Confirm that all instances fields are packed together at the start + EXPECT_GE(klass->NumInstanceFields(), klass->NumReferenceInstanceFields()); + FieldHelper fh; + for (size_t i = 0; i < klass->NumReferenceInstanceFields(); i++) { + Field* field = klass->GetInstanceField(i); + fh.ChangeField(field); + ASSERT_TRUE(!fh.IsPrimitiveType()); + Class* field_type = fh.GetType(); + ASSERT_TRUE(field_type != NULL); + ASSERT_TRUE(!field_type->IsPrimitive()); + } + for (size_t i = klass->NumReferenceInstanceFields(); i < klass->NumInstanceFields(); i++) { + Field* field = klass->GetInstanceField(i); + fh.ChangeField(field); + Class* field_type = fh.GetType(); + ASSERT_TRUE(field_type != NULL); + if (!fh.IsPrimitiveType() || !field_type->IsPrimitive()) { + // While Reference.referent is not primitive, the ClassLinker + // treats it as such so that the garbage collector won't scan it. + EXPECT_EQ(PrettyField(field), "java.lang.Object java.lang.ref.Reference.referent"); + } + } + + size_t total_num_reference_instance_fields = 0; + Class* k = klass; + while (k != NULL) { + total_num_reference_instance_fields += k->NumReferenceInstanceFields(); + k = k->GetSuperClass(); + } + EXPECT_EQ(klass->GetReferenceInstanceOffsets() == 0, + total_num_reference_instance_fields == 0); + } + + void AssertDexFileClass(ClassLoader* class_loader, const std::string& descriptor) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + ASSERT_TRUE(descriptor != NULL); + Class* klass = class_linker_->FindSystemClass(descriptor.c_str()); + ASSERT_TRUE(klass != NULL); + EXPECT_STREQ(descriptor.c_str(), ClassHelper(klass).GetDescriptor()); + EXPECT_EQ(class_loader, klass->GetClassLoader()); + if (klass->IsPrimitive()) { + AssertPrimitiveClass(descriptor, klass); + } else if (klass->IsArrayClass()) { + AssertArrayClass(descriptor, klass); + } else { + AssertClass(descriptor, klass); + } + } + + void AssertDexFile(const DexFile* dex, ClassLoader* class_loader) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + ASSERT_TRUE(dex != NULL); + + // Verify all the classes defined in this file + for (size_t i = 0; i < dex->NumClassDefs(); i++) { + const DexFile::ClassDef& class_def = dex->GetClassDef(i); + const char* descriptor = dex->GetClassDescriptor(class_def); + AssertDexFileClass(class_loader, descriptor); + } + // Verify all the types referenced by this file + for (size_t i = 0; i < dex->NumTypeIds(); i++) { + const DexFile::TypeId& type_id = dex->GetTypeId(i); + const char* descriptor = dex->GetTypeDescriptor(type_id); + AssertDexFileClass(class_loader, descriptor); + } + class_linker_->VisitRoots(TestRootVisitor, NULL); + // Verify the dex cache has resolution methods in all resolved method slots + DexCache* dex_cache = class_linker_->FindDexCache(*dex); + ObjectArray* resolved_methods = dex_cache->GetResolvedMethods(); + for (size_t i = 0; i < static_cast(resolved_methods->GetLength()); i++) { + EXPECT_TRUE(resolved_methods->Get(i) != NULL); + } + } + + static void TestRootVisitor(const Object* root, void*) { + EXPECT_TRUE(root != NULL); + } +}; + +struct CheckOffset { + size_t cpp_offset; + const char* java_name; + CheckOffset(size_t c, const char* j) : cpp_offset(c), java_name(j) {} +}; + +template +struct CheckOffsets { + CheckOffsets(bool is_static, const char* class_descriptor) + : is_static(is_static), class_descriptor(class_descriptor) {} + bool is_static; + std::string class_descriptor; + std::vector offsets; + + bool Check() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + Class* klass = Runtime::Current()->GetClassLinker()->FindSystemClass(class_descriptor.c_str()); + CHECK(klass != NULL) << class_descriptor; + + bool error = false; + + if (!klass->IsClassClass() && !is_static) { + size_t expected_size = is_static ? klass->GetClassSize(): klass->GetObjectSize(); + if (sizeof(T) != expected_size) { + LOG(ERROR) << "Class size mismatch:" + << " class=" << class_descriptor + << " Java=" << expected_size + << " C++=" << sizeof(T); + error = true; + } + } + + size_t num_fields = is_static ? klass->NumStaticFields() : klass->NumInstanceFields(); + if (offsets.size() != num_fields) { + LOG(ERROR) << "Field count mismatch:" + << " class=" << class_descriptor + << " Java=" << num_fields + << " C++=" << offsets.size(); + error = true; + } + + FieldHelper fh; + for (size_t i = 0; i < offsets.size(); i++) { + Field* field = is_static ? klass->GetStaticField(i) : klass->GetInstanceField(i); + fh.ChangeField(field); + StringPiece field_name(fh.GetName()); + if (field_name != offsets[i].java_name) { + error = true; + } + } + if (error) { + for (size_t i = 0; i < offsets.size(); i++) { + CheckOffset& offset = offsets[i]; + Field* field = is_static ? klass->GetStaticField(i) : klass->GetInstanceField(i); + fh.ChangeField(field); + StringPiece field_name(fh.GetName()); + if (field_name != offsets[i].java_name) { + LOG(ERROR) << "JAVA FIELD ORDER MISMATCH NEXT LINE:"; + } + LOG(ERROR) << "Java field order:" + << " i=" << i << " class=" << class_descriptor + << " Java=" << field_name + << " CheckOffsets=" << offset.java_name; + } + } + + for (size_t i = 0; i < offsets.size(); i++) { + CheckOffset& offset = offsets[i]; + Field* field = is_static ? klass->GetStaticField(i) : klass->GetInstanceField(i); + if (field->GetOffset().Uint32Value() != offset.cpp_offset) { + error = true; + } + } + if (error) { + for (size_t i = 0; i < offsets.size(); i++) { + CheckOffset& offset = offsets[i]; + Field* field = is_static ? klass->GetStaticField(i) : klass->GetInstanceField(i); + if (field->GetOffset().Uint32Value() != offset.cpp_offset) { + LOG(ERROR) << "OFFSET MISMATCH NEXT LINE:"; + } + LOG(ERROR) << "Offset: class=" << class_descriptor << " field=" << offset.java_name + << " Java=" << field->GetOffset().Uint32Value() << " C++=" << offset.cpp_offset; + } + } + + return !error; + }; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(CheckOffsets); +}; + +// Note that ClassLinkerTest.ValidateFieldOrderOfJavaCppUnionClasses +// is first since if it is failing, others are unlikely to succeed. + +struct ObjectOffsets : public CheckOffsets { + ObjectOffsets() : CheckOffsets(false, "Ljava/lang/Object;") { + // alphabetical references + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Object, klass_), "shadow$_klass_")); + + // alphabetical 32-bit + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Object, monitor_), "shadow$_monitor_")); + }; +}; + +struct FieldOffsets : public CheckOffsets { + FieldOffsets() : CheckOffsets(false, "Ljava/lang/reflect/Field;") { + // alphabetical references + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Field, declaring_class_), "declaringClass")); + + // alphabetical 32-bit + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Field, access_flags_), "accessFlags")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Field, field_dex_idx_), "fieldDexIndex")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Field, offset_), "offset")); + }; +}; + +struct AbstractMethodOffsets : public CheckOffsets { + AbstractMethodOffsets() : CheckOffsets(false, "Ljava/lang/reflect/AbstractMethod;") { + // alphabetical references + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, declaring_class_), "declaringClass")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, dex_cache_initialized_static_storage_), "dexCacheInitializedStaticStorage")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, dex_cache_resolved_methods_), "dexCacheResolvedMethods")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, dex_cache_resolved_types_), "dexCacheResolvedTypes")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, dex_cache_strings_), "dexCacheStrings")); + + // alphabetical 32-bit + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, access_flags_), "accessFlags")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, code_item_offset_), "codeItemOffset")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, core_spill_mask_), "coreSpillMask")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, entry_point_from_compiled_code_), "entryPointFromCompiledCode")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, entry_point_from_interpreter_), "entryPointFromInterpreter")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, fp_spill_mask_), "fpSpillMask")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, frame_size_in_bytes_), "frameSizeInBytes")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, gc_map_), "gcMap")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, mapping_table_), "mappingTable")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, method_dex_index_), "methodDexIndex")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, method_index_), "methodIndex")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, native_method_), "nativeMethod")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethod, vmap_table_), "vmapTable")); + }; +}; + +struct ConstructorOffsets : public CheckOffsets { + // java.lang.reflect.Constructor is a subclass of java.lang.reflect.AbstractMethod + ConstructorOffsets() : CheckOffsets(false, "Ljava/lang/reflect/Constructor;") { + } +}; + +struct MethodOffsets : public CheckOffsets { + // java.lang.reflect.Method is a subclass of java.lang.reflect.AbstractMethod + MethodOffsets() : CheckOffsets(false, "Ljava/lang/reflect/Method;") { + } +}; + +struct ClassOffsets : public CheckOffsets { + ClassOffsets() : CheckOffsets(false, "Ljava/lang/Class;") { + // alphabetical references + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, class_loader_), "classLoader")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, component_type_), "componentType")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, dex_cache_), "dexCache")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, direct_methods_), "directMethods")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, ifields_), "iFields")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, iftable_), "ifTable")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, name_), "name")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, sfields_), "sFields")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, super_class_), "superClass")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, verify_error_class_), "verifyErrorClass")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, virtual_methods_), "virtualMethods")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, vtable_), "vtable")); + + // alphabetical 32-bit + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, access_flags_), "accessFlags")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, class_size_), "classSize")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, clinit_thread_id_), "clinitThreadId")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, dex_type_idx_), "dexTypeIndex")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, num_reference_instance_fields_), "numReferenceInstanceFields")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, num_reference_static_fields_), "numReferenceStaticFields")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, object_size_), "objectSize")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, primitive_type_), "primitiveType")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, reference_instance_offsets_), "referenceInstanceOffsets")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, reference_static_offsets_), "referenceStaticOffsets")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Class, status_), "status")); + }; +}; + +struct StringOffsets : public CheckOffsets { + StringOffsets() : CheckOffsets(false, "Ljava/lang/String;") { + // alphabetical references + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(String, array_), "value")); + + // alphabetical 32-bit + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(String, count_), "count")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(String, hash_code_), "hashCode")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(String, offset_), "offset")); + }; +}; + +struct ThrowableOffsets : public CheckOffsets { + ThrowableOffsets() : CheckOffsets(false, "Ljava/lang/Throwable;") { + // alphabetical references + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Throwable, cause_), "cause")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Throwable, detail_message_), "detailMessage")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Throwable, stack_state_), "stackState")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Throwable, stack_trace_), "stackTrace")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Throwable, suppressed_exceptions_), "suppressedExceptions")); + }; +}; + +struct StackTraceElementOffsets : public CheckOffsets { + StackTraceElementOffsets() : CheckOffsets(false, "Ljava/lang/StackTraceElement;") { + // alphabetical references + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(StackTraceElement, declaring_class_), "declaringClass")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(StackTraceElement, file_name_), "fileName")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(StackTraceElement, method_name_), "methodName")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(StackTraceElement, line_number_), "lineNumber")); + }; +}; + +struct ClassLoaderOffsets : public CheckOffsets { + ClassLoaderOffsets() : CheckOffsets(false, "Ljava/lang/ClassLoader;") { + // alphabetical references + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(ClassLoader, packages_), "packages")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(ClassLoader, parent_), "parent")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(ClassLoader, proxyCache_), "proxyCache")); + }; +}; + +struct ProxyOffsets : public CheckOffsets { + ProxyOffsets() : CheckOffsets(false, "Ljava/lang/reflect/Proxy;") { + // alphabetical references + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(Proxy, h_), "h")); + }; +}; + +struct ClassClassOffsets : public CheckOffsets { + ClassClassOffsets() : CheckOffsets(true, "Ljava/lang/Class;") { + // padding 32-bit + CHECK_EQ(OFFSETOF_MEMBER(ClassClass, padding_) + 4, + OFFSETOF_MEMBER(ClassClass, serialVersionUID_)); + + // alphabetical 64-bit + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(ClassClass, serialVersionUID_), "serialVersionUID")); + }; +}; + +struct StringClassOffsets : public CheckOffsets { + StringClassOffsets() : CheckOffsets(true, "Ljava/lang/String;") { + // alphabetical references + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(StringClass, ASCII_), "ASCII")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(StringClass, CASE_INSENSITIVE_ORDER_), "CASE_INSENSITIVE_ORDER")); + + // padding 32-bit + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(StringClass, REPLACEMENT_CHAR_), "REPLACEMENT_CHAR")); + + // alphabetical 64-bit + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(StringClass, serialVersionUID_), "serialVersionUID")); + }; +}; + +struct FieldClassOffsets : public CheckOffsets { + FieldClassOffsets() : CheckOffsets(true, "Ljava/lang/reflect/Field;") { + // alphabetical references + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(FieldClass, ORDER_BY_NAME_AND_DECLARING_CLASS_), "ORDER_BY_NAME_AND_DECLARING_CLASS")); + }; +}; + +struct MethodClassOffsets : public CheckOffsets { + MethodClassOffsets() : CheckOffsets(true, "Ljava/lang/reflect/Method;") { + // alphabetical references + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(AbstractMethodClass, ORDER_BY_SIGNATURE_), "ORDER_BY_SIGNATURE")); + }; +}; + +struct DexCacheOffsets : public CheckOffsets { + DexCacheOffsets() : CheckOffsets(false, "Ljava/lang/DexCache;") { + // alphabetical references + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(DexCache, initialized_static_storage_), "initializedStaticStorage")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(DexCache, location_), "location")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(DexCache, resolved_fields_), "resolvedFields")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(DexCache, resolved_methods_), "resolvedMethods")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(DexCache, resolved_types_), "resolvedTypes")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(DexCache, strings_), "strings")); + offsets.push_back(CheckOffset(OFFSETOF_MEMBER(DexCache, dex_file_), "dexFile")); + }; +}; + +// C++ fields must exactly match the fields in the Java classes. If this fails, +// reorder the fields in the C++ class. Managed class fields are ordered by +// ClassLinker::LinkFields. +TEST_F(ClassLinkerTest, ValidateFieldOrderOfJavaCppUnionClasses) { + ScopedObjectAccess soa(Thread::Current()); + EXPECT_TRUE(ObjectOffsets().Check()); + EXPECT_TRUE(ConstructorOffsets().Check()); + EXPECT_TRUE(MethodOffsets().Check()); + EXPECT_TRUE(FieldOffsets().Check()); + EXPECT_TRUE(AbstractMethodOffsets().Check()); + EXPECT_TRUE(ClassOffsets().Check()); + EXPECT_TRUE(StringOffsets().Check()); + EXPECT_TRUE(ThrowableOffsets().Check()); + EXPECT_TRUE(StackTraceElementOffsets().Check()); + EXPECT_TRUE(ClassLoaderOffsets().Check()); + EXPECT_TRUE(ProxyOffsets().Check()); + EXPECT_TRUE(DexCacheOffsets().Check()); + + EXPECT_TRUE(ClassClassOffsets().Check()); + EXPECT_TRUE(StringClassOffsets().Check()); + EXPECT_TRUE(FieldClassOffsets().Check()); + EXPECT_TRUE(MethodClassOffsets().Check()); +} + +TEST_F(ClassLinkerTest, FindClassNonexistent) { + ScopedObjectAccess soa(Thread::Current()); + AssertNonExistentClass("NoSuchClass;"); + AssertNonExistentClass("LNoSuchClass;"); +} + +TEST_F(ClassLinkerTest, FindClassNested) { + ScopedObjectAccess soa(Thread::Current()); + SirtRef class_loader(soa.Self(), soa.Decode(LoadDex("Nested"))); + + Class* outer = class_linker_->FindClass("LNested;", class_loader.get()); + ASSERT_TRUE(outer != NULL); + EXPECT_EQ(0U, outer->NumVirtualMethods()); + EXPECT_EQ(1U, outer->NumDirectMethods()); + + Class* inner = class_linker_->FindClass("LNested$Inner;", class_loader.get()); + ASSERT_TRUE(inner != NULL); + EXPECT_EQ(0U, inner->NumVirtualMethods()); + EXPECT_EQ(1U, inner->NumDirectMethods()); +} + +TEST_F(ClassLinkerTest, FindClass_Primitives) { + ScopedObjectAccess soa(Thread::Current()); + const std::string expected("BCDFIJSZV"); + for (int ch = 1; ch < 256; ++ch) { + std::string descriptor; + descriptor.push_back(ch); + if (expected.find(ch) == std::string::npos) { + AssertNonExistentClass(descriptor); + } else { + AssertPrimitiveClass(descriptor); + } + } +} + +TEST_F(ClassLinkerTest, FindClass) { + ScopedObjectAccess soa(Thread::Current()); + Class* JavaLangObject = class_linker_->FindSystemClass("Ljava/lang/Object;"); + ClassHelper kh(JavaLangObject); + ASSERT_TRUE(JavaLangObject != NULL); + ASSERT_TRUE(JavaLangObject->GetClass() != NULL); + ASSERT_EQ(JavaLangObject->GetClass(), JavaLangObject->GetClass()->GetClass()); + EXPECT_EQ(JavaLangObject, JavaLangObject->GetClass()->GetSuperClass()); + ASSERT_STREQ(kh.GetDescriptor(), "Ljava/lang/Object;"); + EXPECT_TRUE(JavaLangObject->GetSuperClass() == NULL); + EXPECT_FALSE(JavaLangObject->HasSuperClass()); + EXPECT_TRUE(JavaLangObject->GetClassLoader() == NULL); + EXPECT_EQ(Class::kStatusResolved, JavaLangObject->GetStatus()); + EXPECT_FALSE(JavaLangObject->IsErroneous()); + EXPECT_TRUE(JavaLangObject->IsLoaded()); + EXPECT_TRUE(JavaLangObject->IsResolved()); + EXPECT_FALSE(JavaLangObject->IsVerified()); + EXPECT_FALSE(JavaLangObject->IsInitialized()); + EXPECT_FALSE(JavaLangObject->IsArrayInstance()); + EXPECT_FALSE(JavaLangObject->IsArrayClass()); + EXPECT_TRUE(JavaLangObject->GetComponentType() == NULL); + EXPECT_FALSE(JavaLangObject->IsInterface()); + EXPECT_TRUE(JavaLangObject->IsPublic()); + EXPECT_FALSE(JavaLangObject->IsFinal()); + EXPECT_FALSE(JavaLangObject->IsPrimitive()); + EXPECT_FALSE(JavaLangObject->IsSynthetic()); + EXPECT_EQ(2U, JavaLangObject->NumDirectMethods()); + EXPECT_EQ(11U, JavaLangObject->NumVirtualMethods()); + EXPECT_EQ(2U, JavaLangObject->NumInstanceFields()); + FieldHelper fh(JavaLangObject->GetInstanceField(0)); + EXPECT_STREQ(fh.GetName(), "shadow$_klass_"); + fh.ChangeField(JavaLangObject->GetInstanceField(1)); + EXPECT_STREQ(fh.GetName(), "shadow$_monitor_"); + + EXPECT_EQ(0U, JavaLangObject->NumStaticFields()); + EXPECT_EQ(0U, kh.NumDirectInterfaces()); + + SirtRef class_loader(soa.Self(), soa.Decode(LoadDex("MyClass"))); + AssertNonExistentClass("LMyClass;"); + Class* MyClass = class_linker_->FindClass("LMyClass;", class_loader.get()); + kh.ChangeClass(MyClass); + ASSERT_TRUE(MyClass != NULL); + ASSERT_TRUE(MyClass->GetClass() != NULL); + ASSERT_EQ(MyClass->GetClass(), MyClass->GetClass()->GetClass()); + EXPECT_EQ(JavaLangObject, MyClass->GetClass()->GetSuperClass()); + ASSERT_STREQ(kh.GetDescriptor(), "LMyClass;"); + EXPECT_TRUE(MyClass->GetSuperClass() == JavaLangObject); + EXPECT_TRUE(MyClass->HasSuperClass()); + EXPECT_EQ(class_loader.get(), MyClass->GetClassLoader()); + EXPECT_EQ(Class::kStatusResolved, MyClass->GetStatus()); + EXPECT_FALSE(MyClass->IsErroneous()); + EXPECT_TRUE(MyClass->IsLoaded()); + EXPECT_TRUE(MyClass->IsResolved()); + EXPECT_FALSE(MyClass->IsVerified()); + EXPECT_FALSE(MyClass->IsInitialized()); + EXPECT_FALSE(MyClass->IsArrayInstance()); + EXPECT_FALSE(MyClass->IsArrayClass()); + EXPECT_TRUE(MyClass->GetComponentType() == NULL); + EXPECT_FALSE(MyClass->IsInterface()); + EXPECT_FALSE(MyClass->IsPublic()); + EXPECT_FALSE(MyClass->IsFinal()); + EXPECT_FALSE(MyClass->IsPrimitive()); + EXPECT_FALSE(MyClass->IsSynthetic()); + EXPECT_EQ(1U, MyClass->NumDirectMethods()); + EXPECT_EQ(0U, MyClass->NumVirtualMethods()); + EXPECT_EQ(0U, MyClass->NumInstanceFields()); + EXPECT_EQ(0U, MyClass->NumStaticFields()); + EXPECT_EQ(0U, kh.NumDirectInterfaces()); + + EXPECT_EQ(JavaLangObject->GetClass()->GetClass(), MyClass->GetClass()->GetClass()); + + // created by class_linker + AssertArrayClass("[C", "C", NULL); + AssertArrayClass("[Ljava/lang/Object;", "Ljava/lang/Object;", NULL); + // synthesized on the fly + AssertArrayClass("[[C", "[C", NULL); + AssertArrayClass("[[[LMyClass;", "[[LMyClass;", class_loader.get()); + // or not available at all + AssertNonExistentClass("[[[[LNonExistentClass;"); +} + +TEST_F(ClassLinkerTest, LibCore) { + ScopedObjectAccess soa(Thread::Current()); + AssertDexFile(java_lang_dex_file_, NULL); +} + +// The first reference array element must be a multiple of 4 bytes from the +// start of the object +TEST_F(ClassLinkerTest, ValidateObjectArrayElementsOffset) { + ScopedObjectAccess soa(Thread::Current()); + Class* array_class = class_linker_->FindSystemClass("[Ljava/lang/String;"); + ObjectArray* array = ObjectArray::Alloc(soa.Self(), array_class, 0); + uint32_t array_offset = reinterpret_cast(array); + uint32_t data_offset = + array_offset + ObjectArray::DataOffset(sizeof(String*)).Uint32Value(); + if (sizeof(String*) == sizeof(int32_t)) { + EXPECT_TRUE(IsAligned<4>(data_offset)); // Check 4 byte alignment. + } else { + EXPECT_TRUE(IsAligned<8>(data_offset)); // Check 8 byte alignment. + } +} + +TEST_F(ClassLinkerTest, ValidatePrimitiveArrayElementsOffset) { + ScopedObjectAccess soa(Thread::Current()); + SirtRef long_array(soa.Self(), LongArray::Alloc(soa.Self(), 0)); + EXPECT_EQ(class_linker_->FindSystemClass("[J"), long_array->GetClass()); + uintptr_t data_offset = reinterpret_cast(long_array->GetData()); + EXPECT_TRUE(IsAligned<8>(data_offset)); // Longs require 8 byte alignment + + SirtRef double_array(soa.Self(), DoubleArray::Alloc(soa.Self(), 0)); + EXPECT_EQ(class_linker_->FindSystemClass("[D"), double_array->GetClass()); + data_offset = reinterpret_cast(double_array->GetData()); + EXPECT_TRUE(IsAligned<8>(data_offset)); // Doubles require 8 byte alignment + + SirtRef int_array(soa.Self(), IntArray::Alloc(soa.Self(), 0)); + EXPECT_EQ(class_linker_->FindSystemClass("[I"), int_array->GetClass()); + data_offset = reinterpret_cast(int_array->GetData()); + EXPECT_TRUE(IsAligned<4>(data_offset)); // Ints require 4 byte alignment + + SirtRef char_array(soa.Self(), CharArray::Alloc(soa.Self(), 0)); + EXPECT_EQ(class_linker_->FindSystemClass("[C"), char_array->GetClass()); + data_offset = reinterpret_cast(char_array->GetData()); + EXPECT_TRUE(IsAligned<2>(data_offset)); // Chars require 2 byte alignment + + SirtRef short_array(soa.Self(), ShortArray::Alloc(soa.Self(), 0)); + EXPECT_EQ(class_linker_->FindSystemClass("[S"), short_array->GetClass()); + data_offset = reinterpret_cast(short_array->GetData()); + EXPECT_TRUE(IsAligned<2>(data_offset)); // Shorts require 2 byte alignment + + // Take it as given that bytes and booleans have byte alignment +} + +TEST_F(ClassLinkerTest, ValidateBoxedTypes) { + // Validate that the "value" field is always the 0th field in each of java.lang's box classes. + // This lets UnboxPrimitive avoid searching for the field by name at runtime. + ScopedObjectAccess soa(Thread::Current()); + Class* c; + c = class_linker_->FindClass("Ljava/lang/Boolean;", NULL); + FieldHelper fh(c->GetIFields()->Get(0)); + EXPECT_STREQ("value", fh.GetName()); + c = class_linker_->FindClass("Ljava/lang/Byte;", NULL); + fh.ChangeField(c->GetIFields()->Get(0)); + EXPECT_STREQ("value", fh.GetName()); + c = class_linker_->FindClass("Ljava/lang/Character;", NULL); + fh.ChangeField(c->GetIFields()->Get(0)); + EXPECT_STREQ("value", fh.GetName()); + c = class_linker_->FindClass("Ljava/lang/Double;", NULL); + fh.ChangeField(c->GetIFields()->Get(0)); + EXPECT_STREQ("value", fh.GetName()); + c = class_linker_->FindClass("Ljava/lang/Float;", NULL); + fh.ChangeField(c->GetIFields()->Get(0)); + EXPECT_STREQ("value", fh.GetName()); + c = class_linker_->FindClass("Ljava/lang/Integer;", NULL); + fh.ChangeField(c->GetIFields()->Get(0)); + EXPECT_STREQ("value", fh.GetName()); + c = class_linker_->FindClass("Ljava/lang/Long;", NULL); + fh.ChangeField(c->GetIFields()->Get(0)); + EXPECT_STREQ("value", fh.GetName()); + c = class_linker_->FindClass("Ljava/lang/Short;", NULL); + fh.ChangeField(c->GetIFields()->Get(0)); + EXPECT_STREQ("value", fh.GetName()); +} + +TEST_F(ClassLinkerTest, TwoClassLoadersOneClass) { + ScopedObjectAccess soa(Thread::Current()); + SirtRef class_loader_1(soa.Self(), soa.Decode(LoadDex("MyClass"))); + SirtRef class_loader_2(soa.Self(), soa.Decode(LoadDex("MyClass"))); + Class* MyClass_1 = class_linker_->FindClass("LMyClass;", class_loader_1.get()); + Class* MyClass_2 = class_linker_->FindClass("LMyClass;", class_loader_2.get()); + EXPECT_TRUE(MyClass_1 != NULL); + EXPECT_TRUE(MyClass_2 != NULL); + EXPECT_NE(MyClass_1, MyClass_2); +} + +TEST_F(ClassLinkerTest, StaticFields) { + ScopedObjectAccess soa(Thread::Current()); + SirtRef class_loader(soa.Self(), soa.Decode(LoadDex("Statics"))); + Class* statics = class_linker_->FindClass("LStatics;", class_loader.get()); + class_linker_->EnsureInitialized(statics, true, true); + + // Static final primitives that are initialized by a compile-time constant + // expression resolve to a copy of a constant value from the constant pool. + // So should be null. + AbstractMethod* clinit = statics->FindDirectMethod("", "()V"); + EXPECT_TRUE(clinit == NULL); + + EXPECT_EQ(9U, statics->NumStaticFields()); + + Field* s0 = statics->FindStaticField("s0", "Z"); + FieldHelper fh(s0); + EXPECT_STREQ(ClassHelper(s0->GetClass()).GetDescriptor(), "Ljava/lang/reflect/Field;"); + EXPECT_TRUE(fh.GetTypeAsPrimitiveType() == Primitive::kPrimBoolean); + EXPECT_EQ(true, s0->GetBoolean(statics)); + s0->SetBoolean(statics, false); + + Field* s1 = statics->FindStaticField("s1", "B"); + fh.ChangeField(s1); + EXPECT_TRUE(fh.GetTypeAsPrimitiveType() == Primitive::kPrimByte); + EXPECT_EQ(5, s1->GetByte(statics)); + s1->SetByte(statics, 6); + + Field* s2 = statics->FindStaticField("s2", "C"); + fh.ChangeField(s2); + EXPECT_TRUE(fh.GetTypeAsPrimitiveType() == Primitive::kPrimChar); + EXPECT_EQ('a', s2->GetChar(statics)); + s2->SetChar(statics, 'b'); + + Field* s3 = statics->FindStaticField("s3", "S"); + fh.ChangeField(s3); + EXPECT_TRUE(fh.GetTypeAsPrimitiveType() == Primitive::kPrimShort); + EXPECT_EQ(-536, s3->GetShort(statics)); + s3->SetShort(statics, -535); + + Field* s4 = statics->FindStaticField("s4", "I"); + fh.ChangeField(s4); + EXPECT_TRUE(fh.GetTypeAsPrimitiveType() == Primitive::kPrimInt); + EXPECT_EQ(2000000000, s4->GetInt(statics)); + s4->SetInt(statics, 2000000001); + + Field* s5 = statics->FindStaticField("s5", "J"); + fh.ChangeField(s5); + EXPECT_TRUE(fh.GetTypeAsPrimitiveType() == Primitive::kPrimLong); + EXPECT_EQ(0x1234567890abcdefLL, s5->GetLong(statics)); + s5->SetLong(statics, 0x34567890abcdef12LL); + + Field* s6 = statics->FindStaticField("s6", "F"); + fh.ChangeField(s6); + EXPECT_TRUE(fh.GetTypeAsPrimitiveType() == Primitive::kPrimFloat); + EXPECT_EQ(0.5, s6->GetFloat(statics)); + s6->SetFloat(statics, 0.75); + + Field* s7 = statics->FindStaticField("s7", "D"); + fh.ChangeField(s7); + EXPECT_TRUE(fh.GetTypeAsPrimitiveType() == Primitive::kPrimDouble); + EXPECT_EQ(16777217, s7->GetDouble(statics)); + s7->SetDouble(statics, 16777219); + + Field* s8 = statics->FindStaticField("s8", "Ljava/lang/String;"); + fh.ChangeField(s8); + EXPECT_TRUE(fh.GetTypeAsPrimitiveType() == Primitive::kPrimNot); + EXPECT_TRUE(s8->GetObject(statics)->AsString()->Equals("android")); + s8->SetObject(s8->GetDeclaringClass(), String::AllocFromModifiedUtf8(soa.Self(), "robot")); + + // TODO: Remove EXPECT_FALSE when GCC can handle EXPECT_EQ + // http://code.google.com/p/googletest/issues/detail?id=322 + EXPECT_FALSE( s0->GetBoolean(statics)); + EXPECT_EQ(6, s1->GetByte(statics)); + EXPECT_EQ('b', s2->GetChar(statics)); + EXPECT_EQ(-535, s3->GetShort(statics)); + EXPECT_EQ(2000000001, s4->GetInt(statics)); + EXPECT_EQ(0x34567890abcdef12LL, s5->GetLong(statics)); + EXPECT_EQ(0.75, s6->GetFloat(statics)); + EXPECT_EQ(16777219, s7->GetDouble(statics)); + EXPECT_TRUE(s8->GetObject(statics)->AsString()->Equals("robot")); +} + +TEST_F(ClassLinkerTest, Interfaces) { + ScopedObjectAccess soa(Thread::Current()); + SirtRef class_loader(soa.Self(), soa.Decode(LoadDex("Interfaces"))); + Class* I = class_linker_->FindClass("LInterfaces$I;", class_loader.get()); + Class* J = class_linker_->FindClass("LInterfaces$J;", class_loader.get()); + Class* K = class_linker_->FindClass("LInterfaces$K;", class_loader.get()); + Class* A = class_linker_->FindClass("LInterfaces$A;", class_loader.get()); + Class* B = class_linker_->FindClass("LInterfaces$B;", class_loader.get()); + EXPECT_TRUE(I->IsAssignableFrom(A)); + EXPECT_TRUE(J->IsAssignableFrom(A)); + EXPECT_TRUE(J->IsAssignableFrom(K)); + EXPECT_TRUE(K->IsAssignableFrom(B)); + EXPECT_TRUE(J->IsAssignableFrom(B)); + + AbstractMethod* Ii = I->FindVirtualMethod("i", "()V"); + AbstractMethod* Jj1 = J->FindVirtualMethod("j1", "()V"); + AbstractMethod* Jj2 = J->FindVirtualMethod("j2", "()V"); + AbstractMethod* Kj1 = K->FindInterfaceMethod("j1", "()V"); + AbstractMethod* Kj2 = K->FindInterfaceMethod("j2", "()V"); + AbstractMethod* Kk = K->FindInterfaceMethod("k", "()V"); + AbstractMethod* Ai = A->FindVirtualMethod("i", "()V"); + AbstractMethod* Aj1 = A->FindVirtualMethod("j1", "()V"); + AbstractMethod* Aj2 = A->FindVirtualMethod("j2", "()V"); + ASSERT_TRUE(Ii != NULL); + ASSERT_TRUE(Jj1 != NULL); + ASSERT_TRUE(Jj2 != NULL); + ASSERT_TRUE(Kj1 != NULL); + ASSERT_TRUE(Kj2 != NULL); + ASSERT_TRUE(Kk != NULL); + ASSERT_TRUE(Ai != NULL); + ASSERT_TRUE(Aj1 != NULL); + ASSERT_TRUE(Aj2 != NULL); + EXPECT_NE(Ii, Ai); + EXPECT_NE(Jj1, Aj1); + EXPECT_NE(Jj2, Aj2); + EXPECT_EQ(Kj1, Jj1); + EXPECT_EQ(Kj2, Jj2); + EXPECT_EQ(Ai, A->FindVirtualMethodForInterface(Ii)); + EXPECT_EQ(Aj1, A->FindVirtualMethodForInterface(Jj1)); + EXPECT_EQ(Aj2, A->FindVirtualMethodForInterface(Jj2)); + EXPECT_EQ(Ai, A->FindVirtualMethodForVirtualOrInterface(Ii)); + EXPECT_EQ(Aj1, A->FindVirtualMethodForVirtualOrInterface(Jj1)); + EXPECT_EQ(Aj2, A->FindVirtualMethodForVirtualOrInterface(Jj2)); + + Field* Afoo = A->FindStaticField("foo", "Ljava/lang/String;"); + Field* Bfoo = B->FindStaticField("foo", "Ljava/lang/String;"); + Field* Jfoo = J->FindStaticField("foo", "Ljava/lang/String;"); + Field* Kfoo = K->FindStaticField("foo", "Ljava/lang/String;"); + ASSERT_TRUE(Afoo != NULL); + EXPECT_EQ(Afoo, Bfoo); + EXPECT_EQ(Afoo, Jfoo); + EXPECT_EQ(Afoo, Kfoo); +} + +TEST_F(ClassLinkerTest, ResolveVerifyAndClinit) { + // pretend we are trying to get the static storage for the StaticsFromCode class. + + // case 1, get the uninitialized storage from StaticsFromCode. + // case 2, get the initialized storage from StaticsFromCode.getS0 + + ScopedObjectAccess soa(Thread::Current()); + jobject jclass_loader = LoadDex("StaticsFromCode"); + SirtRef class_loader(soa.Self(), soa.Decode(jclass_loader)); + const DexFile* dex_file = Runtime::Current()->GetCompileTimeClassPath(jclass_loader)[0]; + CHECK(dex_file != NULL); + + Class* klass = class_linker_->FindClass("LStaticsFromCode;", class_loader.get()); + AbstractMethod* clinit = klass->FindDirectMethod("", "()V"); + AbstractMethod* getS0 = klass->FindDirectMethod("getS0", "()Ljava/lang/Object;"); + const DexFile::StringId* string_id = dex_file->FindStringId("LStaticsFromCode;"); + ASSERT_TRUE(string_id != NULL); + const DexFile::TypeId* type_id = dex_file->FindTypeId(dex_file->GetIndexForStringId(*string_id)); + ASSERT_TRUE(type_id != NULL); + uint32_t type_idx = dex_file->GetIndexForTypeId(*type_id); + EXPECT_TRUE(clinit->GetDexCacheInitializedStaticStorage()->Get(type_idx) == NULL); + StaticStorageBase* uninit = ResolveVerifyAndClinit(type_idx, clinit, Thread::Current(), true, false); + EXPECT_TRUE(uninit != NULL); + EXPECT_TRUE(clinit->GetDexCacheInitializedStaticStorage()->Get(type_idx) == NULL); + StaticStorageBase* init = ResolveVerifyAndClinit(type_idx, getS0, Thread::Current(), true, false); + EXPECT_TRUE(init != NULL); + EXPECT_EQ(init, clinit->GetDexCacheInitializedStaticStorage()->Get(type_idx)); +} + +TEST_F(ClassLinkerTest, FinalizableBit) { + ScopedObjectAccess soa(Thread::Current()); + Class* c; + + // Object has a finalize method, but we know it's empty. + c = class_linker_->FindSystemClass("Ljava/lang/Object;"); + EXPECT_FALSE(c->IsFinalizable()); + + // Enum has a finalize method to prevent its subclasses from implementing one. + c = class_linker_->FindSystemClass("Ljava/lang/Enum;"); + EXPECT_FALSE(c->IsFinalizable()); + + // RoundingMode is an enum. + c = class_linker_->FindSystemClass("Ljava/math/RoundingMode;"); + EXPECT_FALSE(c->IsFinalizable()); + + // RandomAccessFile extends Object and overrides finalize. + c = class_linker_->FindSystemClass("Ljava/io/RandomAccessFile;"); + EXPECT_TRUE(c->IsFinalizable()); + + // FileInputStream is finalizable and extends InputStream which isn't. + c = class_linker_->FindSystemClass("Ljava/io/InputStream;"); + EXPECT_FALSE(c->IsFinalizable()); + c = class_linker_->FindSystemClass("Ljava/io/FileInputStream;"); + EXPECT_TRUE(c->IsFinalizable()); + + // ScheduledThreadPoolExecutor doesn't have a finalize method but + // extends ThreadPoolExecutor which does. + c = class_linker_->FindSystemClass("Ljava/util/concurrent/ThreadPoolExecutor;"); + EXPECT_TRUE(c->IsFinalizable()); + c = class_linker_->FindSystemClass("Ljava/util/concurrent/ScheduledThreadPoolExecutor;"); + EXPECT_TRUE(c->IsFinalizable()); +} + +TEST_F(ClassLinkerTest, ClassRootDescriptors) { + ScopedObjectAccess soa(Thread::Current()); + ClassHelper kh; + for (int i = 0; i < ClassLinker::kClassRootsMax; i++) { + Class* klass = class_linker_->GetClassRoot(ClassLinker::ClassRoot(i)); + kh.ChangeClass(klass); + EXPECT_TRUE(kh.GetDescriptor() != NULL); + EXPECT_STREQ(kh.GetDescriptor(), + class_linker_->GetClassRootDescriptor(ClassLinker::ClassRoot(i))) << " i = " << i; + } +} + +} // namespace art diff --git a/src/closure.h b/src/closure.h new file mode 100644 index 0000000000000000000000000000000000000000..17f2b84d82236a49869d541dcd04e5d3f4d7e719 --- /dev/null +++ b/src/closure.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_SRC_CLOSURE_H_ +#define ART_SRC_CLOSURE_H_ + +namespace art { + +class Thread; + +class Closure { + public: + virtual ~Closure() { } + virtual void Run(Thread* self) = 0; +}; + +} // namespace art + +#endif // ART_SRC_CLOSURE_H_ diff --git a/src/common_test.cc b/src/common_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..5df7d4129fe1fcb90b7fb8aceda4fa82527ee972 --- /dev/null +++ b/src/common_test.cc @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/logging.h" +#include "gtest/gtest.h" + +int main(int argc, char **argv) { + art::InitLogging(argv); + LOG(INFO) << "Running main() from common_test.cc..."; + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/common_test.h b/src/common_test.h new file mode 100644 index 0000000000000000000000000000000000000000..a40537f8b5d790115fc7a212262173c74779b716 --- /dev/null +++ b/src/common_test.h @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "../../external/icu4c/common/unicode/uvernum.h" +#include "base/macros.h" +#include "base/stl_util.h" +#include "base/stringprintf.h" +#include "base/unix_file/fd_file.h" +#include "class_linker.h" +#include "compiler/driver/compiler_driver.h" +#include "dex_file-inl.h" +#include "gtest/gtest.h" +#include "heap.h" +#include "instruction_set.h" +#include "mirror/class_loader.h" +#include "oat_file.h" +#include "object_utils.h" +#include "os.h" +#include "runtime.h" +#include "runtime_support.h" +#include "scoped_thread_state_change.h" +#include "ScopedLocalRef.h" +#include "thread.h" +#include "UniquePtr.h" +#include "well_known_classes.h" + +namespace art { + +static const byte kBase64Map[256] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, + 255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, // NOLINT + 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, // NOLINT + 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, // NOLINT + 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, // NOLINT + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255 +}; + +byte* DecodeBase64(const char* src, size_t* dst_size) { + std::vector tmp; + uint32_t t = 0, y = 0; + int g = 3; + for (size_t i = 0; src[i] != '\0'; ++i) { + byte c = kBase64Map[src[i] & 0xFF]; + if (c == 255) continue; + // the final = symbols are read and used to trim the remaining bytes + if (c == 254) { + c = 0; + // prevent g < 0 which would potentially allow an overflow later + if (--g < 0) { + *dst_size = 0; + return NULL; + } + } else if (g != 3) { + // we only allow = to be at the end + *dst_size = 0; + return NULL; + } + t = (t << 6) | c; + if (++y == 4) { + tmp.push_back((t >> 16) & 255); + if (g > 1) { + tmp.push_back((t >> 8) & 255); + } + if (g > 2) { + tmp.push_back(t & 255); + } + y = t = 0; + } + } + if (y != 0) { + *dst_size = 0; + return NULL; + } + UniquePtr dst(new byte[tmp.size()]); + if (dst_size != NULL) { + *dst_size = tmp.size(); + } else { + *dst_size = 0; + } + std::copy(tmp.begin(), tmp.end(), dst.get()); + return dst.release(); +} + +class ScratchFile { + public: + ScratchFile() { + filename_ = getenv("ANDROID_DATA"); + filename_ += "/TmpFile-XXXXXX"; + int fd = mkstemp(&filename_[0]); + CHECK_NE(-1, fd); + file_.reset(new File(fd, GetFilename())); + } + + ~ScratchFile() { + int unlink_result = unlink(filename_.c_str()); + CHECK_EQ(0, unlink_result); + } + + const std::string& GetFilename() const { + return filename_; + } + + File* GetFile() const { + return file_.get(); + } + + int GetFd() const { + return file_->Fd(); + } + + private: + std::string filename_; + UniquePtr file_; +}; + +class CommonTest : public testing::Test { + public: + static void MakeExecutable(const mirror::ByteArray* code_array) { + CHECK(code_array != NULL); + MakeExecutable(code_array->GetData(), code_array->GetLength()); + } + + static void MakeExecutable(const std::vector& code) { + CHECK_NE(code.size(), 0U); + MakeExecutable(&code[0], code.size()); + } + + // Create an OatMethod based on pointers (for unit tests) + OatFile::OatMethod CreateOatMethod(const void* code, + const size_t frame_size_in_bytes, + const uint32_t core_spill_mask, + const uint32_t fp_spill_mask, + const uint32_t* mapping_table, + const uint16_t* vmap_table, + const uint8_t* gc_map) { + return OatFile::OatMethod(NULL, + reinterpret_cast(code), + frame_size_in_bytes, + core_spill_mask, + fp_spill_mask, + reinterpret_cast(mapping_table), + reinterpret_cast(vmap_table), + reinterpret_cast(gc_map) + ); + } + + void MakeExecutable(mirror::AbstractMethod* method) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + CHECK(method != NULL); + LOG(INFO) << "MakeExecutable " << PrettyMethod(method); + + const CompiledMethod* compiled_method = NULL; + if (!method->IsAbstract()) { + const mirror::DexCache* dex_cache = method->GetDeclaringClass()->GetDexCache(); + const DexFile& dex_file = *dex_cache->GetDexFile(); + compiled_method = + compiler_driver_->GetCompiledMethod(CompilerDriver::MethodReference(&dex_file, + method->GetDexMethodIndex())); + +#ifndef ART_LIGHT_MODE + CHECK(compiled_method != NULL) << PrettyMethod(method); +#endif + } + if (compiled_method != NULL) { + const std::vector& code = compiled_method->GetCode(); + MakeExecutable(code); + const void* method_code = CompiledMethod::CodePointer(&code[0], + compiled_method->GetInstructionSet()); + LOG(INFO) << "MakeExecutable " << PrettyMethod(method) << " code=" << method_code; + OatFile::OatMethod oat_method = CreateOatMethod(method_code, + compiled_method->GetFrameSizeInBytes(), + compiled_method->GetCoreSpillMask(), + compiled_method->GetFpSpillMask(), + &compiled_method->GetMappingTable()[0], + &compiled_method->GetVmapTable()[0], + NULL); + oat_method.LinkMethod(method); + } else { + const void* method_code; + if (method->IsAbstract()) { + method_code = GetAbstractMethodErrorStub(); + } else { + // No code? You must mean to go into the interpreter. + method_code = GetInterpreterEntryPoint(); + } + LOG(INFO) << "MakeExecutable " << PrettyMethod(method) << " code=" << method_code; + OatFile::OatMethod oat_method = CreateOatMethod(method_code, + kStackAlignment, + 0, + 0, + NULL, + NULL, + NULL); + oat_method.LinkMethod(method); + } + } + + static void MakeExecutable(const void* code_start, size_t code_length) { + CHECK(code_start != NULL); + CHECK_NE(code_length, 0U); + uintptr_t data = reinterpret_cast(code_start); + uintptr_t base = RoundDown(data, kPageSize); + uintptr_t limit = RoundUp(data + code_length, kPageSize); + uintptr_t len = limit - base; + int result = mprotect(reinterpret_cast(base), len, PROT_READ | PROT_WRITE | PROT_EXEC); + CHECK_EQ(result, 0); + + // Flush instruction cache + // Only uses __builtin___clear_cache if GCC >= 4.3.3 +#if GCC_VERSION >= 40303 + __builtin___clear_cache(reinterpret_cast(base), reinterpret_cast(base + len)); +#else + LOG(FATAL) << "UNIMPLEMENTED: cache flush"; +#endif + } + + static void SetEnvironmentVariables(std::string& android_data) { + if (IsHost()) { + // $ANDROID_ROOT is set on the device, but not on the host. + // We need to set this so that icu4c can find its locale data. + std::string root; + root += getenv("ANDROID_BUILD_TOP"); +#if defined(__linux__) + root += "/out/host/linux-x86"; +#elif defined(__APPLE__) + root += "/out/host/darwin-x86"; +#else +#error unsupported OS +#endif + setenv("ANDROID_ROOT", root.c_str(), 1); + setenv("LD_LIBRARY_PATH", ":", 0); // Required by java.lang.System.. + } + + // On target, Cannot use /mnt/sdcard because it is mounted noexec, so use subdir of art-cache + android_data = (IsHost() ? "/tmp/art-data-XXXXXX" : "/data/art-cache/art-data-XXXXXX"); + if (mkdtemp(&android_data[0]) == NULL) { + PLOG(FATAL) << "mkdtemp(\"" << &android_data[0] << "\") failed"; + } + setenv("ANDROID_DATA", android_data.c_str(), 1); + } + + protected: + static bool IsHost() { + return (getenv("ANDROID_BUILD_TOP") != NULL); + } + + virtual void SetUp() { + SetEnvironmentVariables(android_data_); + art_cache_.append(android_data_.c_str()); + art_cache_.append("/art-cache"); + int mkdir_result = mkdir(art_cache_.c_str(), 0700); + ASSERT_EQ(mkdir_result, 0); + + java_lang_dex_file_ = DexFile::Open(GetLibCoreDexFileName(), GetLibCoreDexFileName()); + if (java_lang_dex_file_ == NULL) { + LOG(FATAL) << "Could not open .dex file '" << GetLibCoreDexFileName() << "'\n"; + } + conscrypt_file_ = DexFile::Open(GetConscryptFileName(), GetConscryptFileName()); + if (conscrypt_file_ == NULL) { + LOG(FATAL) << "Could not open .dex file '" << GetConscryptFileName() << "'\n"; + } + boot_class_path_.push_back(java_lang_dex_file_); + boot_class_path_.push_back(conscrypt_file_); + + std::string min_heap_string(StringPrintf("-Xms%zdm", Heap::kDefaultInitialSize / MB)); + std::string max_heap_string(StringPrintf("-Xmx%zdm", Heap::kDefaultMaximumSize / MB)); + + Runtime::Options options; + options.push_back(std::make_pair("compiler", reinterpret_cast(NULL))); + options.push_back(std::make_pair("bootclasspath", &boot_class_path_)); + options.push_back(std::make_pair("-Xcheck:jni", reinterpret_cast(NULL))); + options.push_back(std::make_pair(min_heap_string.c_str(), reinterpret_cast(NULL))); + options.push_back(std::make_pair(max_heap_string.c_str(), reinterpret_cast(NULL))); + if(!Runtime::Create(options, false)) { + LOG(FATAL) << "Failed to create runtime"; + return; + } + runtime_.reset(Runtime::Current()); + // Runtime::Create acquired the mutator_lock_ that is normally given away when we Runtime::Start, + // give it away now and then switch to a more managable ScopedObjectAccess. + Thread::Current()->TransitionFromRunnableToSuspended(kNative); + // Whilst we're in native take the opportunity to initialize well known classes. + WellKnownClasses::InitClasses(Thread::Current()->GetJniEnv()); + ScopedObjectAccess soa(Thread::Current()); + ASSERT_TRUE(runtime_.get() != NULL); + class_linker_ = runtime_->GetClassLinker(); + + InstructionSet instruction_set = kNone; +#if defined(__arm__) + instruction_set = kThumb2; +#elif defined(__mips__) + instruction_set = kMips; +#elif defined(__i386__) + instruction_set = kX86; +#endif + + // TODO: make selectable +#if defined(ART_USE_PORTABLE_COMPILER) + CompilerBackend compiler_backend = kPortable; +#else + CompilerBackend compiler_backend = kQuick; +#endif + + if (!runtime_->HasResolutionMethod()) { + runtime_->SetResolutionMethod(runtime_->CreateResolutionMethod()); + } + for (int i = 0; i < Runtime::kLastCalleeSaveType; i++) { + Runtime::CalleeSaveType type = Runtime::CalleeSaveType(i); + if (!runtime_->HasCalleeSaveMethod(type)) { + runtime_->SetCalleeSaveMethod( + runtime_->CreateCalleeSaveMethod(instruction_set, type), type); + } + } + class_linker_->FixupDexCaches(runtime_->GetResolutionMethod()); + image_classes_.reset(new std::set); + compiler_driver_.reset(new CompilerDriver(compiler_backend, instruction_set, true, 2, false, + image_classes_.get(), true, true)); + + // Create the heap thread pool so that the GC runs in parallel for tests. Normally, the thread + // pool is created by the runtime. + runtime_->GetHeap()->CreateThreadPool(); + + runtime_->GetHeap()->VerifyHeap(); // Check for heap corruption before the test + } + + virtual void TearDown() { + const char* android_data = getenv("ANDROID_DATA"); + ASSERT_TRUE(android_data != NULL); + DIR* dir = opendir(art_cache_.c_str()); + ASSERT_TRUE(dir != NULL); + dirent* e; + while ((e = readdir(dir)) != NULL) { + if ((strcmp(e->d_name, ".") == 0) || (strcmp(e->d_name, "..") == 0)) { + continue; + } + std::string filename(art_cache_); + filename.push_back('/'); + filename.append(e->d_name); + int unlink_result = unlink(filename.c_str()); + ASSERT_EQ(0, unlink_result); + } + closedir(dir); + int rmdir_cache_result = rmdir(art_cache_.c_str()); + ASSERT_EQ(0, rmdir_cache_result); + int rmdir_data_result = rmdir(android_data_.c_str()); + ASSERT_EQ(0, rmdir_data_result); + + // icu4c has a fixed 10-element array "gCommonICUDataArray". + // If we run > 10 tests, we fill that array and u_setCommonData fails. + // There's a function to clear the array, but it's not public... + typedef void (*IcuCleanupFn)(); + void* sym = dlsym(RTLD_DEFAULT, "u_cleanup_" U_ICU_VERSION_SHORT); + CHECK(sym != NULL); + IcuCleanupFn icu_cleanup_fn = reinterpret_cast(sym); + (*icu_cleanup_fn)(); + + compiler_driver_.reset(); + image_classes_.reset(); + STLDeleteElements(&opened_dex_files_); + + Runtime::Current()->GetHeap()->VerifyHeap(); // Check for heap corruption after the test + } + + std::string GetLibCoreDexFileName() { + return GetDexFileName("core"); + } + + std::string GetConscryptFileName() { + return GetDexFileName("conscrypt"); + } + + std::string GetDexFileName(const std::string& jar_prefix) { + if (IsHost()) { + const char* host_dir = getenv("ANDROID_HOST_OUT"); + CHECK(host_dir != NULL); + return StringPrintf("%s/framework/%s-hostdex.jar", host_dir, jar_prefix.c_str()); + } + return StringPrintf("%s/framework/%s.jar", GetAndroidRoot(), jar_prefix.c_str()); + } + + std::string GetTestAndroidRoot() { + if (IsHost()) { + const char* host_dir = getenv("ANDROID_HOST_OUT"); + CHECK(host_dir != NULL); + return host_dir; + } + return GetAndroidRoot(); + } + + const DexFile* OpenTestDexFile(const char* name) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + CHECK(name != NULL); + std::string filename; + if (IsHost()) { + filename += getenv("ANDROID_HOST_OUT"); + filename += "/framework/"; + } else { + filename += "/data/nativetest/art/"; + } + filename += "art-test-dex-"; + filename += name; + filename += ".jar"; + const DexFile* dex_file = DexFile::Open(filename, filename); + CHECK(dex_file != NULL) << "Failed to open " << filename; + opened_dex_files_.push_back(dex_file); + return dex_file; + } + + jobject LoadDex(const char* dex_name) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + const DexFile* dex_file = OpenTestDexFile(dex_name); + CHECK(dex_file != NULL); + class_linker_->RegisterDexFile(*dex_file); + std::vector class_path; + class_path.push_back(dex_file); + ScopedObjectAccessUnchecked soa(Thread::Current()); + ScopedLocalRef class_loader_local(soa.Env(), + soa.Env()->AllocObject(WellKnownClasses::dalvik_system_PathClassLoader)); + jobject class_loader = soa.Env()->NewGlobalRef(class_loader_local.get()); + soa.Self()->SetClassLoaderOverride(soa.Decode(class_loader_local.get())); + Runtime::Current()->SetCompileTimeClassPath(class_loader, class_path); + return class_loader; + } + + void CompileClass(mirror::ClassLoader* class_loader, const char* class_name) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + std::string class_descriptor(DotToDescriptor(class_name)); + mirror::Class* klass = class_linker_->FindClass(class_descriptor.c_str(), class_loader); + CHECK(klass != NULL) << "Class not found " << class_name; + for (size_t i = 0; i < klass->NumDirectMethods(); i++) { + CompileMethod(klass->GetDirectMethod(i)); + } + for (size_t i = 0; i < klass->NumVirtualMethods(); i++) { + CompileMethod(klass->GetVirtualMethod(i)); + } + } + + void CompileMethod(mirror::AbstractMethod* method) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + CHECK(method != NULL); + compiler_driver_->CompileOne(method); + MakeExecutable(method); + } + + void CompileDirectMethod(mirror::ClassLoader* class_loader, + const char* class_name, + const char* method_name, + const char* signature) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + std::string class_descriptor(DotToDescriptor(class_name)); + mirror::Class* klass = class_linker_->FindClass(class_descriptor.c_str(), class_loader); + CHECK(klass != NULL) << "Class not found " << class_name; + mirror::AbstractMethod* method = klass->FindDirectMethod(method_name, signature); + CHECK(method != NULL) << "Direct method not found: " + << class_name << "." << method_name << signature; + CompileMethod(method); + } + + void CompileVirtualMethod(mirror::ClassLoader* class_loader, + const char* class_name, + const char* method_name, + const char* signature) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + std::string class_descriptor(DotToDescriptor(class_name)); + mirror::Class* klass = class_linker_->FindClass(class_descriptor.c_str(), class_loader); + CHECK(klass != NULL) << "Class not found " << class_name; + mirror::AbstractMethod* method = klass->FindVirtualMethod(method_name, signature); + CHECK(method != NULL) << "Virtual method not found: " + << class_name << "." << method_name << signature; + CompileMethod(method); + } + + void ReserveImageSpace() { + // Reserve where the image will be loaded up front so that other parts of test set up don't + // accidentally end up colliding with the fixed memory address when we need to load the image. + image_reservation_.reset(MemMap::MapAnonymous("image reservation", (byte*)ART_BASE_ADDRESS, + (size_t)100 * 1024 * 1024, // 100MB + PROT_NONE)); + } + + void UnreserveImageSpace() { + image_reservation_.reset(); + } + + std::string android_data_; + std::string art_cache_; + const DexFile* java_lang_dex_file_; // owned by runtime_ + const DexFile* conscrypt_file_; // owned by runtime_ + std::vector boot_class_path_; + UniquePtr runtime_; + // Owned by the runtime + ClassLinker* class_linker_; + UniquePtr compiler_driver_; + UniquePtr > image_classes_; + + private: + std::vector opened_dex_files_; + UniquePtr image_reservation_; +}; + +// Sets a CheckJni abort hook to catch failures. Note that this will cause CheckJNI to carry on +// rather than aborting, so be careful! +class CheckJniAbortCatcher { + public: + CheckJniAbortCatcher() : vm_(Runtime::Current()->GetJavaVM()) { + vm_->check_jni_abort_hook = Hook; + vm_->check_jni_abort_hook_data = &actual_; + } + + ~CheckJniAbortCatcher() { + vm_->check_jni_abort_hook = NULL; + vm_->check_jni_abort_hook_data = NULL; + EXPECT_TRUE(actual_.empty()) << actual_; + } + + void Check(const char* expected_text) { + EXPECT_TRUE(actual_.find(expected_text) != std::string::npos) << "\n" + << "Expected to find: " << expected_text << "\n" + << "In the output : " << actual_; + actual_.clear(); + } + + private: + static void Hook(void* data, const std::string& reason) { + // We use += because when we're hooking the aborts like this, multiple problems can be found. + *reinterpret_cast(data) += reason; + } + + JavaVMExt* vm_; + std::string actual_; + + DISALLOW_COPY_AND_ASSIGN(CheckJniAbortCatcher); +}; + +// TODO: These tests were disabled for portable when we went to having +// MCLinker link LLVM ELF output because we no longer just have code +// blobs in memory. We'll need to dlopen to load and relocate +// temporary output to resurrect these tests. +#if defined(ART_USE_PORTABLE_COMPILER) +#define TEST_DISABLED_FOR_PORTABLE() printf("WARNING: TEST DISABLED FOR PORTABLE\n"); return +#else +#define TEST_DISABLED_FOR_PORTABLE() +#endif + +} // namespace art + +namespace std { + +// TODO: isn't gtest supposed to be able to print STL types for itself? +template +std::ostream& operator<<(std::ostream& os, const std::vector& rhs) { + os << ::art::ToString(rhs); + return os; +} + +} // namespace std diff --git a/src/common_throws.cc b/src/common_throws.cc new file mode 100644 index 0000000000000000000000000000000000000000..dc3627a0b2e1121b93972d5fb19e3d00bae127af --- /dev/null +++ b/src/common_throws.cc @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common_throws.h" + +#include "base/logging.h" +#include "class_linker-inl.h" +#include "dex_file-inl.h" +#include "dex_instruction-inl.h" +#include "invoke_type.h" +#include "mirror/abstract_method-inl.h" +#include "mirror/class-inl.h" +#include "mirror/object-inl.h" +#include "mirror/object_array-inl.h" +#include "object_utils.h" +#include "thread.h" + +#include + +namespace art { + +static void AddReferrerLocation(std::ostream& os, const mirror::Class* referrer) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (referrer != NULL) { + ClassHelper kh(referrer); + std::string location(kh.GetLocation()); + if (!location.empty()) { + os << " (declaration of '" << PrettyDescriptor(referrer) + << "' appears in " << location << ")"; + } + } +} + +static void ThrowException(const ThrowLocation* throw_location, const char* exception_descriptor, + const mirror::Class* referrer, const char* fmt, va_list* args = NULL) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + std::ostringstream msg; + if (args != NULL) { + std::string vmsg; + StringAppendV(&vmsg, fmt, *args); + msg << vmsg; + } else { + msg << fmt; + } + AddReferrerLocation(msg, referrer); + Thread* self = Thread::Current(); + if (throw_location == NULL) { + ThrowLocation computed_throw_location = self->GetCurrentLocationForThrow(); + self->ThrowNewException(computed_throw_location, exception_descriptor, msg.str().c_str()); + } else { + self->ThrowNewException(*throw_location, exception_descriptor, msg.str().c_str()); + } +} + +// ArithmeticException + +void ThrowArithmeticExceptionDivideByZero(Thread* self) { + ThrowException(NULL, "Ljava/lang/ArithmeticException;", NULL, "divide by zero"); +} + +// ArrayIndexOutOfBoundsException + +void ThrowArrayIndexOutOfBoundsException(int index, int length) { + ThrowException(NULL, "Ljava/lang/ArrayIndexOutOfBoundsException;", NULL, + StringPrintf("length=%d; index=%d", length, index).c_str()); +} + +// ArrayStoreException + +void ThrowArrayStoreException(const mirror::Class* element_class, + const mirror::Class* array_class) { + ThrowException(NULL, "Ljava/lang/ArrayStoreException;", NULL, + StringPrintf("%s cannot be stored in an array of type %s", + PrettyDescriptor(element_class).c_str(), + PrettyDescriptor(array_class).c_str()).c_str()); +} + +// ClassCastException + +void ThrowClassCastException(const mirror::Class* dest_type, const mirror::Class* src_type) { + ThrowException(NULL, "Ljava/lang/ClassCastException;", NULL, + StringPrintf("%s cannot be cast to %s", + PrettyDescriptor(src_type).c_str(), + PrettyDescriptor(dest_type).c_str()).c_str()); +} + +void ThrowClassCastException(const ThrowLocation* throw_location, const char* msg) { + ThrowException(throw_location, "Ljava/lang/ClassCastException;", NULL, msg); +} + +// ClassCircularityError + +void ThrowClassCircularityError(mirror::Class* c) { + std::ostringstream msg; + msg << PrettyDescriptor(c); + ThrowException(NULL, "Ljava/lang/ClassCircularityError;", c, msg.str().c_str()); +} + +// ClassFormatError + +void ThrowClassFormatError(const mirror::Class* referrer, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + ThrowException(NULL, "Ljava/lang/ClassFormatError;", referrer, fmt, &args); + va_end(args);} + +// IllegalAccessError + +void ThrowIllegalAccessErrorClass(mirror::Class* referrer, mirror::Class* accessed) { + std::ostringstream msg; + msg << "Illegal class access: '" << PrettyDescriptor(referrer) << "' attempting to access '" + << PrettyDescriptor(accessed) << "'"; + ThrowException(NULL, "Ljava/lang/IllegalAccessError;", referrer, msg.str().c_str()); +} + +void ThrowIllegalAccessErrorClassForMethodDispatch(mirror::Class* referrer, mirror::Class* accessed, + const mirror::AbstractMethod* caller, + const mirror::AbstractMethod* called, + InvokeType type) { + std::ostringstream msg; + msg << "Illegal class access ('" << PrettyDescriptor(referrer) << "' attempting to access '" + << PrettyDescriptor(accessed) << "') in attempt to invoke " << type + << " method " << PrettyMethod(called).c_str(); + ThrowException(NULL, "Ljava/lang/IllegalAccessError;", referrer, msg.str().c_str()); +} + +void ThrowIllegalAccessErrorMethod(mirror::Class* referrer, mirror::AbstractMethod* accessed) { + std::ostringstream msg; + msg << "Method '" << PrettyMethod(accessed) << "' is inaccessible to class '" + << PrettyDescriptor(referrer) << "'"; + ThrowException(NULL, "Ljava/lang/IllegalAccessError;", referrer, msg.str().c_str()); +} + +void ThrowIllegalAccessErrorField(mirror::Class* referrer, mirror::Field* accessed) { + std::ostringstream msg; + msg << "Field '" << PrettyField(accessed, false) << "' is inaccessible to class '" + << PrettyDescriptor(referrer) << "'"; + ThrowException(NULL, "Ljava/lang/IllegalAccessError;", referrer, msg.str().c_str()); +} + +void ThrowIllegalAccessErrorFinalField(const mirror::AbstractMethod* referrer, + mirror::Field* accessed) { + std::ostringstream msg; + msg << "Final field '" << PrettyField(accessed, false) << "' cannot be written to by method '" + << PrettyMethod(referrer) << "'"; + ThrowException(NULL, "Ljava/lang/IllegalAccessError;", referrer != NULL ? referrer->GetClass() : NULL, + msg.str().c_str()); +} + +void ThrowIllegalAccessError(mirror::Class* referrer, const char* fmt, ...){ + va_list args; + va_start(args, fmt); + ThrowException(NULL, "Ljava/lang/IllegalAccessError;", referrer, fmt, &args); + va_end(args); +} + +// IllegalArgumentException + +void ThrowIllegalArgumentException(const ThrowLocation* throw_location, const char* msg) { + ThrowException(throw_location, "Ljava/lang/IllegalArgumentException;", NULL, msg); +} + + +// IncompatibleClassChangeError + +void ThrowIncompatibleClassChangeError(InvokeType expected_type, InvokeType found_type, + mirror::AbstractMethod* method, + const mirror::AbstractMethod* referrer) { + std::ostringstream msg; + msg << "The method '" << PrettyMethod(method) << "' was expected to be of type " + << expected_type << " but instead was found to be of type " << found_type; + ThrowException(NULL, "Ljava/lang/IncompatibleClassChangeError;", + referrer != NULL ? referrer->GetClass() : NULL, + msg.str().c_str()); +} + +void ThrowIncompatibleClassChangeErrorClassForInterfaceDispatch(const mirror::AbstractMethod* interface_method, + mirror::Object* this_object, + const mirror::AbstractMethod* referrer) { + // Referrer is calling interface_method on this_object, however, the interface_method isn't + // implemented by this_object. + CHECK(this_object != NULL); + std::ostringstream msg; + msg << "Class '" << PrettyDescriptor(this_object->GetClass()) + << "' does not implement interface '" + << PrettyDescriptor(interface_method->GetDeclaringClass()) + << "' in call to '" << PrettyMethod(interface_method) << "'"; + ThrowException(NULL, "Ljava/lang/IncompatibleClassChangeError;", + referrer != NULL ? referrer->GetClass() : NULL, + msg.str().c_str()); +} + +void ThrowIncompatibleClassChangeErrorField(const mirror::Field* resolved_field, bool is_static, + const mirror::AbstractMethod* referrer) { + std::ostringstream msg; + msg << "Expected '" << PrettyField(resolved_field) << "' to be a " + << (is_static ? "static" : "instance") << " field" << " rather than a " + << (is_static ? "instance" : "static") << " field"; + ThrowException(NULL, "Ljava/lang/IncompatibleClassChangeError;", referrer->GetClass(), + msg.str().c_str()); +} + +void ThrowIncompatibleClassChangeError(const mirror::Class* referrer, const char* fmt, ...){ + va_list args; + va_start(args, fmt); + ThrowException(NULL, "Ljava/lang/IncompatibleClassChangeError;", referrer, fmt, &args); + va_end(args); +} + +// LinkageError + +void ThrowLinkageError(const mirror::Class* referrer, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + ThrowException(NULL, "Ljava/lang/LinkageError;", referrer, fmt, &args); + va_end(args); +} + +// NegativeArraySizeException + +void ThrowNegativeArraySizeException(int size) { + ThrowException(NULL, "Ljava/lang/NegativeArraySizeException;", NULL, StringPrintf("%d", size).c_str()); +} + +void ThrowNegativeArraySizeException(const char* msg) { + ThrowException(NULL, "Ljava/lang/NegativeArraySizeException;", NULL, msg); +} + +// NoSuchFieldError + +void ThrowNoSuchFieldError(const StringPiece& scope, mirror::Class* c, + const StringPiece& type, const StringPiece& name) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + ClassHelper kh(c); + std::ostringstream msg; + msg << "No " << scope << "field " << name << " of type " << type + << " in class " << kh.GetDescriptor() << " or its superclasses"; + ThrowException(NULL, "Ljava/lang/NoSuchFieldError;", c, msg.str().c_str()); +} + +// NoSuchMethodError + +void ThrowNoSuchMethodError(InvokeType type, mirror::Class* c, const StringPiece& name, + const StringPiece& signature) { + std::ostringstream msg; + ClassHelper kh(c); + msg << "No " << type << " method " << name << signature + << " in class " << kh.GetDescriptor() << " or its super classes"; + ThrowException(NULL, "Ljava/lang/NoSuchMethodError;", c, msg.str().c_str()); +} + +void ThrowNoSuchMethodError(uint32_t method_idx) { + Thread* self = Thread::Current(); + ThrowLocation throw_location = self->GetCurrentLocationForThrow(); + mirror::DexCache* dex_cache = throw_location.GetMethod()->GetDeclaringClass()->GetDexCache(); + const DexFile& dex_file = *dex_cache->GetDexFile(); + std::ostringstream msg; + msg << "No method '" << PrettyMethod(method_idx, dex_file, true) << "'"; + ThrowException(&throw_location, "Ljava/lang/NoSuchMethodError;", + throw_location.GetMethod()->GetDeclaringClass(), msg.str().c_str()); +} + +// NullPointerException + +void ThrowNullPointerExceptionForFieldAccess(const ThrowLocation& throw_location, + mirror::Field* field, bool is_read) { + std::ostringstream msg; + msg << "Attempt to " << (is_read ? "read from" : "write to") + << " field '" << PrettyField(field, true) << "' on a null object reference"; + ThrowException(&throw_location, "Ljava/lang/NullPointerException;", NULL, msg.str().c_str()); +} + +void ThrowNullPointerExceptionForMethodAccess(const ThrowLocation& throw_location, uint32_t method_idx, + InvokeType type) { + mirror::DexCache* dex_cache = throw_location.GetMethod()->GetDeclaringClass()->GetDexCache(); + const DexFile& dex_file = *dex_cache->GetDexFile(); + std::ostringstream msg; + msg << "Attempt to invoke " << type << " method '" + << PrettyMethod(method_idx, dex_file, true) << "' on a null object reference"; + ThrowException(&throw_location, "Ljava/lang/NullPointerException;", NULL, msg.str().c_str()); +} + +void ThrowNullPointerExceptionFromDexPC(const ThrowLocation& throw_location) { + const DexFile::CodeItem* code = MethodHelper(throw_location.GetMethod()).GetCodeItem(); + uint32_t throw_dex_pc = throw_location.GetDexPc(); + CHECK_LT(throw_dex_pc, code->insns_size_in_code_units_); + const Instruction* instr = Instruction::At(&code->insns_[throw_dex_pc]); + switch (instr->Opcode()) { + case Instruction::INVOKE_DIRECT: + ThrowNullPointerExceptionForMethodAccess(throw_location, instr->VRegB_35c(), kDirect); + break; + case Instruction::INVOKE_DIRECT_RANGE: + ThrowNullPointerExceptionForMethodAccess(throw_location, instr->VRegB_3rc(), kDirect); + break; + case Instruction::INVOKE_VIRTUAL: + ThrowNullPointerExceptionForMethodAccess(throw_location, instr->VRegB_35c(), kVirtual); + break; + case Instruction::INVOKE_VIRTUAL_RANGE: + ThrowNullPointerExceptionForMethodAccess(throw_location, instr->VRegB_3rc(), kVirtual); + break; + case Instruction::INVOKE_INTERFACE: + ThrowNullPointerExceptionForMethodAccess(throw_location, instr->VRegB_35c(), kInterface); + break; + case Instruction::INVOKE_INTERFACE_RANGE: + ThrowNullPointerExceptionForMethodAccess(throw_location, instr->VRegB_3rc(), kInterface); + break; + case Instruction::IGET: + case Instruction::IGET_WIDE: + case Instruction::IGET_OBJECT: + case Instruction::IGET_BOOLEAN: + case Instruction::IGET_BYTE: + case Instruction::IGET_CHAR: + case Instruction::IGET_SHORT: { + mirror::Field* field = + Runtime::Current()->GetClassLinker()->ResolveField(instr->VRegC_22c(), + throw_location.GetMethod(), false); + ThrowNullPointerExceptionForFieldAccess(throw_location, field, true /* read */); + break; + } + case Instruction::IPUT: + case Instruction::IPUT_WIDE: + case Instruction::IPUT_OBJECT: + case Instruction::IPUT_BOOLEAN: + case Instruction::IPUT_BYTE: + case Instruction::IPUT_CHAR: + case Instruction::IPUT_SHORT: { + mirror::Field* field = + Runtime::Current()->GetClassLinker()->ResolveField(instr->VRegC_22c(), + throw_location.GetMethod(), false); + ThrowNullPointerExceptionForFieldAccess(throw_location, field, false /* write */); + break; + } + case Instruction::AGET: + case Instruction::AGET_WIDE: + case Instruction::AGET_OBJECT: + case Instruction::AGET_BOOLEAN: + case Instruction::AGET_BYTE: + case Instruction::AGET_CHAR: + case Instruction::AGET_SHORT: + ThrowException(&throw_location, "Ljava/lang/NullPointerException;", NULL, + "Attempt to read from null array"); + break; + case Instruction::APUT: + case Instruction::APUT_WIDE: + case Instruction::APUT_OBJECT: + case Instruction::APUT_BOOLEAN: + case Instruction::APUT_BYTE: + case Instruction::APUT_CHAR: + case Instruction::APUT_SHORT: + ThrowException(&throw_location, "Ljava/lang/NullPointerException;", NULL, + "Attempt to write to null array"); + break; + case Instruction::ARRAY_LENGTH: + ThrowException(&throw_location, "Ljava/lang/NullPointerException;", NULL, + "Attempt to get length of null array"); + break; + default: { + // TODO: We should have covered all the cases where we expect a NPE above, this + // message/logging is so we can improve any cases we've missed in the future. + const DexFile& dex_file = + *throw_location.GetMethod()->GetDeclaringClass()->GetDexCache()->GetDexFile(); + ThrowException(&throw_location, "Ljava/lang/NullPointerException;", NULL, + StringPrintf("Null pointer exception during instruction '%s'", + instr->DumpString(&dex_file).c_str()).c_str()); + break; + } + } +} + +void ThrowNullPointerException(const ThrowLocation* throw_location, const char* msg) { + ThrowException(throw_location, "Ljava/lang/NullPointerException;", NULL, msg); +} + +// RuntimeException + +void ThrowRuntimeException(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + ThrowException(NULL, "Ljava/lang/RuntimeException;", NULL, fmt, &args); + va_end(args); +} + +// VerifyError + +void ThrowVerifyError(const mirror::Class* referrer, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + ThrowException(NULL, "Ljava/lang/VerifyError;", referrer, fmt, &args); + va_end(args); +} + +} // namespace art diff --git a/src/common_throws.h b/src/common_throws.h new file mode 100644 index 0000000000000000000000000000000000000000..55554350519bfda67fb3bfd7d6a79e311b2b01ed --- /dev/null +++ b/src/common_throws.h @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_SRC_COMMON_THROWS__H_ +#define ART_SRC_COMMON_THROWS_H_ + +#include "base/mutex.h" +#include "invoke_type.h" + +namespace art { +namespace mirror { +class AbstractMethod; +class Class; +class Field; +class Object; +} // namespace mirror +class StringPiece; +class ThrowLocation; + +// ArithmeticException + +void ThrowArithmeticExceptionDivideByZero(Thread* self) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +// ArrayIndexOutOfBoundsException + +void ThrowArrayIndexOutOfBoundsException(int index, int length) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +// ArrayStoreException + +void ThrowArrayStoreException(const mirror::Class* element_class, + const mirror::Class* array_class) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +// ClassCircularityError + +void ThrowClassCircularityError(mirror::Class* c) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +// ClassCastException + +void ThrowClassCastException(const mirror::Class* dest_type, const mirror::Class* src_type) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +void ThrowClassCastException(const ThrowLocation* throw_location, const char* msg) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +// ClassFormatError + +void ThrowClassFormatError(const mirror::Class* referrer, const char* fmt, ...) + __attribute__((__format__(__printf__, 2, 3))) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +// IllegalAccessError + +void ThrowIllegalAccessErrorClass(mirror::Class* referrer, mirror::Class* accessed) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +void ThrowIllegalAccessErrorClassForMethodDispatch(mirror::Class* referrer, mirror::Class* accessed, + const mirror::AbstractMethod* caller, + const mirror::AbstractMethod* called, + InvokeType type) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +void ThrowIllegalAccessErrorMethod(mirror::Class* referrer, mirror::AbstractMethod* accessed) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +void ThrowIllegalAccessErrorField(mirror::Class* referrer, mirror::Field* accessed) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +void ThrowIllegalAccessErrorFinalField(const mirror::AbstractMethod* referrer, + mirror::Field* accessed) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +void ThrowIllegalAccessError(mirror::Class* referrer, const char* fmt, ...) + __attribute__((__format__(__printf__, 2, 3))) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +// IllegalArgumentException + +void ThrowIllegalArgumentException(const ThrowLocation* throw_location, const char* msg) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +// IncompatibleClassChangeError + +void ThrowIncompatibleClassChangeError(InvokeType expected_type, InvokeType found_type, + mirror::AbstractMethod* method, + const mirror::AbstractMethod* referrer) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +void ThrowIncompatibleClassChangeErrorClassForInterfaceDispatch(const mirror::AbstractMethod* interface_method, + mirror::Object* this_object, + const mirror::AbstractMethod* referrer) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +void ThrowIncompatibleClassChangeErrorField(const mirror::Field* resolved_field, bool is_static, + const mirror::AbstractMethod* referrer) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +void ThrowIncompatibleClassChangeError(const mirror::Class* referrer, const char* fmt, ...) + __attribute__((__format__(__printf__, 2, 3))) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +// LinkageError + +void ThrowLinkageError(const mirror::Class* referrer, const char* fmt, ...) + __attribute__((__format__(__printf__, 2, 3))) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +// NegativeArraySizeException + +void ThrowNegativeArraySizeException(int size) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +void ThrowNegativeArraySizeException(const char* msg) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + +// NoSuchFieldError + +void ThrowNoSuchFieldError(const StringPiece& scope, mirror::Class* c, + const StringPiece& type, const StringPiece& name) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +// NoSuchMethodError + +void ThrowNoSuchMethodError(InvokeType type, mirror::Class* c, const StringPiece& name, + const StringPiece& signature) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +void ThrowNoSuchMethodError(uint32_t method_idx) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +// NullPointerException + +void ThrowNullPointerExceptionForFieldAccess(const ThrowLocation& throw_location, + mirror::Field* field, + bool is_read) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +void ThrowNullPointerExceptionForMethodAccess(const ThrowLocation& throw_location, + uint32_t method_idx, + InvokeType type) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +void ThrowNullPointerExceptionFromDexPC(const ThrowLocation& throw_location) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +void ThrowNullPointerException(const ThrowLocation* throw_location, const char* msg) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +// RuntimeException + +void ThrowRuntimeException(const char* fmt, ...) + __attribute__((__format__(__printf__, 1, 2))) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +// VerifyError + +void ThrowVerifyError(const mirror::Class* referrer, const char* fmt, ...) + __attribute__((__format__(__printf__, 2, 3))) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + +} // namespace art + +#endif // ART_SRC_COMMON_THROWS_H_ diff --git a/src/compiled_class.h b/src/compiled_class.h new file mode 100644 index 0000000000000000000000000000000000000000..f050ee6a7e1ef9fc2e45e0538fabcfb14e68736b --- /dev/null +++ b/src/compiled_class.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2011 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_SRC_COMPILED_CLASS_H_ +#define ART_SRC_COMPILED_CLASS_H_ + +#include "mirror/class.h" + +namespace art { + +class CompiledClass { + public: + explicit CompiledClass(mirror::Class::Status status) : status_(status) {} + ~CompiledClass() {} + mirror::Class::Status GetStatus() const { + return status_; + } + private: + const mirror::Class::Status status_; +}; + +} // namespace art + +#endif // ART_SRC_COMPILED_CLASS_H_ diff --git a/src/compiled_method.cc b/src/compiled_method.cc new file mode 100644 index 0000000000000000000000000000000000000000..757a324155ee03b6bb00a9084d93469b765a5021 --- /dev/null +++ b/src/compiled_method.cc @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compiled_method.h" + +namespace art { + +CompiledCode::CompiledCode(InstructionSet instruction_set, const std::vector& code) + : instruction_set_(instruction_set), code_(code) +{ + CHECK_NE(code.size(), 0U); +} + +CompiledCode::CompiledCode(InstructionSet instruction_set, + const std::string& elf_object, + const std::string& symbol) + : instruction_set_(instruction_set), symbol_(symbol) { + CHECK_NE(elf_object.size(), 0U); + CHECK_NE(symbol.size(), 0U); + // TODO: we shouldn't just shove ELF objects in as "code" but + // change to have different kinds of compiled methods. This is + // being deferred until we work on hybrid execution or at least + // until we work on batch compilation. + code_.resize(elf_object.size()); + memcpy(&code_[0], &elf_object[0], elf_object.size()); +} + +uint32_t CompiledCode::AlignCode(uint32_t offset) const { + return AlignCode(offset, instruction_set_); +} + +uint32_t CompiledCode::AlignCode(uint32_t offset, InstructionSet instruction_set) { + switch (instruction_set) { + case kArm: + case kThumb2: + return RoundUp(offset, kArmAlignment); + case kMips: + return RoundUp(offset, kMipsAlignment); + case kX86: + return RoundUp(offset, kX86Alignment); + default: + LOG(FATAL) << "Unknown InstructionSet: " << instruction_set; + return 0; + } +} + +size_t CompiledCode::CodeDelta() const { + switch (instruction_set_) { + case kArm: + case kMips: + case kX86: + return 0; + case kThumb2: { + // +1 to set the low-order bit so a BLX will switch to Thumb mode + return 1; + } + default: + LOG(FATAL) << "Unknown InstructionSet: " << instruction_set_; + return 0; + } +} + +const void* CompiledCode::CodePointer(const void* code_pointer, + InstructionSet instruction_set) { + switch (instruction_set) { + case kArm: + case kMips: + case kX86: + return code_pointer; + case kThumb2: { + uintptr_t address = reinterpret_cast(code_pointer); + // Set the low-order bit so a BLX will switch to Thumb mode + address |= 0x1; + return reinterpret_cast(address); + } + default: + LOG(FATAL) << "Unknown InstructionSet: " << instruction_set; + return NULL; + } +} + +#if defined(ART_USE_PORTABLE_COMPILER) +const std::string& CompiledCode::GetSymbol() const { + CHECK_NE(0U, symbol_.size()); + return symbol_; +} + +const std::vector& CompiledCode::GetOatdataOffsetsToCompliledCodeOffset() const { + CHECK_NE(0U, oatdata_offsets_to_compiled_code_offset_.size()) << symbol_; + return oatdata_offsets_to_compiled_code_offset_; +} + +void CompiledCode::AddOatdataOffsetToCompliledCodeOffset(uint32_t offset) { + oatdata_offsets_to_compiled_code_offset_.push_back(offset); +} +#endif + +CompiledMethod::CompiledMethod(InstructionSet instruction_set, + const std::vector& code, + const size_t frame_size_in_bytes, + const uint32_t core_spill_mask, + const uint32_t fp_spill_mask, + const std::vector& mapping_table, + const std::vector& vmap_table, + const std::vector& native_gc_map) + : CompiledCode(instruction_set, code), frame_size_in_bytes_(frame_size_in_bytes), + core_spill_mask_(core_spill_mask), fp_spill_mask_(fp_spill_mask), + gc_map_(native_gc_map) +{ + DCHECK_EQ(vmap_table.size(), + static_cast(__builtin_popcount(core_spill_mask) + + __builtin_popcount(fp_spill_mask))); + CHECK_LE(vmap_table.size(), (1U << 16) - 1); // length must fit in 2^16-1 + + std::vector length_prefixed_mapping_table; + length_prefixed_mapping_table.push_back(mapping_table.size()); + length_prefixed_mapping_table.insert(length_prefixed_mapping_table.end(), + mapping_table.begin(), + mapping_table.end()); + DCHECK_EQ(mapping_table.size() + 1, length_prefixed_mapping_table.size()); + + std::vector length_prefixed_vmap_table; + length_prefixed_vmap_table.push_back(vmap_table.size()); + length_prefixed_vmap_table.insert(length_prefixed_vmap_table.end(), + vmap_table.begin(), + vmap_table.end()); + DCHECK_EQ(vmap_table.size() + 1, length_prefixed_vmap_table.size()); + DCHECK_EQ(vmap_table.size(), length_prefixed_vmap_table[0]); + + mapping_table_ = length_prefixed_mapping_table; + vmap_table_ = length_prefixed_vmap_table; + DCHECK_EQ(vmap_table_[0], static_cast(__builtin_popcount(core_spill_mask) + __builtin_popcount(fp_spill_mask))); +} + +CompiledMethod::CompiledMethod(InstructionSet instruction_set, + const std::vector& code, + const size_t frame_size_in_bytes, + const uint32_t core_spill_mask, + const uint32_t fp_spill_mask) + : CompiledCode(instruction_set, code), + frame_size_in_bytes_(frame_size_in_bytes), + core_spill_mask_(core_spill_mask), fp_spill_mask_(fp_spill_mask) {} + +} // namespace art diff --git a/src/compiled_method.h b/src/compiled_method.h new file mode 100644 index 0000000000000000000000000000000000000000..fb0172cc19f287f2be395f9020a3d7e628940749 --- /dev/null +++ b/src/compiled_method.h @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2011 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_SRC_COMPILED_METHOD_H_ +#define ART_SRC_COMPILED_METHOD_H_ + +#include +#include + +#include "instruction_set.h" +#include "utils.h" +#include "UniquePtr.h" + +namespace llvm { + class Function; +} // namespace llvm + +namespace art { + +class CompiledCode { + public: + // For Quick to supply an code blob + CompiledCode(InstructionSet instruction_set, const std::vector& code); + + // For Portable to supply an ELF object + CompiledCode(InstructionSet instruction_set, + const std::string& elf_object, + const std::string &symbol); + + InstructionSet GetInstructionSet() const { + return instruction_set_; + } + + const std::vector& GetCode() const { + return code_; + } + + void SetCode(const std::vector& code) { + CHECK_NE(code.size(), 0U); + code_ = code; + } + + bool operator==(const CompiledCode& rhs) const { + return (code_ == rhs.code_); + } + + // To align an offset from a page-aligned value to make it suitable + // for code storage. For example on ARM, to ensure that PC relative + // valu computations work out as expected. + uint32_t AlignCode(uint32_t offset) const; + static uint32_t AlignCode(uint32_t offset, InstructionSet instruction_set); + + // returns the difference between the code address and a usable PC. + // mainly to cope with kThumb2 where the lower bit must be set. + size_t CodeDelta() const; + + // Returns a pointer suitable for invoking the code at the argument + // code_pointer address. Mainly to cope with kThumb2 where the + // lower bit must be set to indicate Thumb mode. + static const void* CodePointer(const void* code_pointer, + InstructionSet instruction_set); + +#if defined(ART_USE_PORTABLE_COMPILER) + const std::string& GetSymbol() const; + const std::vector& GetOatdataOffsetsToCompliledCodeOffset() const; + void AddOatdataOffsetToCompliledCodeOffset(uint32_t offset); +#endif + + private: + const InstructionSet instruction_set_; + + // Used to store the PIC code for Quick and an ELF image for portable. + std::vector code_; + + // Used for the Portable ELF symbol name. + const std::string symbol_; + + // There are offsets from the oatdata symbol to where the offset to + // the compiled method will be found. These are computed by the + // OatWriter and then used by the ElfWriter to add relocations so + // that MCLinker can update the values to the location in the linked .so. + std::vector oatdata_offsets_to_compiled_code_offset_; +}; + +class CompiledMethod : public CompiledCode { + public: + // Constructs a CompiledMethod for the non-LLVM compilers. + CompiledMethod(InstructionSet instruction_set, + const std::vector& code, + const size_t frame_size_in_bytes, + const uint32_t core_spill_mask, + const uint32_t fp_spill_mask, + const std::vector& mapping_table, + const std::vector& vmap_table, + const std::vector& native_gc_map); + + // Constructs a CompiledMethod for the JniCompiler. + CompiledMethod(InstructionSet instruction_set, + const std::vector& code, + const size_t frame_size_in_bytes, + const uint32_t core_spill_mask, + const uint32_t fp_spill_mask); + + // Constructs a CompiledMethod for the Portable compiler. + CompiledMethod(InstructionSet instruction_set, + const std::string& code, + const std::vector& gc_map, + const std::string& symbol) + : CompiledCode(instruction_set, code, symbol), + frame_size_in_bytes_(kStackAlignment), core_spill_mask_(0), + fp_spill_mask_(0), gc_map_(gc_map) { + } + + // Constructs a CompiledMethod for the Portable JniCompiler. + CompiledMethod(InstructionSet instruction_set, + const std::string& code, + const std::string& symbol) + : CompiledCode(instruction_set, code, symbol), + frame_size_in_bytes_(kStackAlignment), core_spill_mask_(0), + fp_spill_mask_(0) { + } + + ~CompiledMethod() {} + + size_t GetFrameSizeInBytes() const { + return frame_size_in_bytes_; + } + + uint32_t GetCoreSpillMask() const { + return core_spill_mask_; + } + + uint32_t GetFpSpillMask() const { + return fp_spill_mask_; + } + + const std::vector& GetMappingTable() const { + return mapping_table_; + } + + const std::vector& GetVmapTable() const { + return vmap_table_; + } + + const std::vector& GetGcMap() const { + return gc_map_; + } + + private: + // For quick code, the size of the activation used by the code. + const size_t frame_size_in_bytes_; + // For quick code, a bit mask describing spilled GPR callee-save registers. + const uint32_t core_spill_mask_; + // For quick code, a bit mask describing spilled FPR callee-save registers. + const uint32_t fp_spill_mask_; + // For quick code, a map from native PC offset to dex PC. + std::vector mapping_table_; + // For quick code, a map from GPR/FPR register to dex register. + std::vector vmap_table_; + // For quick code, a map keyed by native PC indices to bitmaps describing what dalvik registers + // are live. For portable code, the key is a dalvik PC. + std::vector gc_map_; +}; + +} // namespace art + +#endif // ART_SRC_COMPILED_METHOD_H_ diff --git a/src/compiler/dex/arena_allocator.cc b/src/compiler/dex/arena_allocator.cc new file mode 100644 index 0000000000000000000000000000000000000000..3a3e3858b3274acc02476ecd39a0bfc095b43181 --- /dev/null +++ b/src/compiler/dex/arena_allocator.cc @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compiler_internals.h" +#include "dex_file-inl.h" +#include "arena_allocator.h" +#include "base/logging.h" + +namespace art { + +static const char* alloc_names[ArenaAllocator::kNumAllocKinds] = { + "Misc ", + "BasicBlock ", + "LIR ", + "MIR ", + "DataFlow ", + "GrowList ", + "GrowBitMap ", + "Dalvik2SSA ", + "DebugInfo ", + "Successor ", + "RegAlloc ", + "Data ", + "Preds ", +}; + +ArenaAllocator::ArenaAllocator(size_t default_size) + : default_size_(default_size), + block_size_(default_size - sizeof(ArenaMemBlock)), + arena_head_(NULL), + current_block_(NULL), + num_arena_blocks_(0), + malloc_bytes_(0), + lost_bytes_(0), + num_allocations_(0) { + memset(&alloc_stats_[0], 0, sizeof(alloc_stats_)); + // Start with an empty arena. + arena_head_ = current_block_ = EmptyArenaBlock(); + num_arena_blocks_++; +} + +ArenaAllocator::~ArenaAllocator() { + // Reclaim all the arena blocks allocated so far. + ArenaMemBlock* head = arena_head_; + while (head != NULL) { + ArenaMemBlock* p = head; + head = head->next; + free(p); + } + arena_head_ = NULL; + num_arena_blocks_ = 0; +} + +// Return an arena with no storage for use as a sentinal. +ArenaAllocator::ArenaMemBlock* ArenaAllocator::EmptyArenaBlock() { + ArenaMemBlock* res = static_cast(malloc(sizeof(ArenaMemBlock))); + malloc_bytes_ += sizeof(ArenaMemBlock); + res->block_size = 0; + res->bytes_allocated = 0; + res->next = NULL; + return res; +} + +// Arena-based malloc for compilation tasks. +void* ArenaAllocator::NewMem(size_t size, bool zero, ArenaAllocKind kind) { + DCHECK(current_block_ != NULL); + DCHECK(arena_head_ != NULL); + size = (size + 3) & ~3; + alloc_stats_[kind] += size; + ArenaMemBlock* allocation_block = current_block_; // Assume we'll fit. + size_t remaining_space = current_block_->block_size - current_block_->bytes_allocated; + if (remaining_space < size) { + /* + * Time to allocate a new block. If this is a large allocation or we have + * significant space remaining in the current block then fulfill the allocation + * request with a custom-sized malloc() - otherwise grab a new standard block. + */ + size_t allocation_size = sizeof(ArenaMemBlock); + if ((remaining_space >= ARENA_HIGH_WATER) || (size > block_size_)) { + allocation_size += size; + } else { + allocation_size += block_size_; + } + ArenaMemBlock *new_block = static_cast(malloc(allocation_size)); + if (new_block == NULL) { + LOG(FATAL) << "Arena allocation failure"; + } + malloc_bytes_ += allocation_size; + new_block->block_size = allocation_size - sizeof(ArenaMemBlock); + new_block->bytes_allocated = 0; + new_block->next = NULL; + num_arena_blocks_++; + /* + * If the new block is completely full, insert it into the head of the list so we don't + * bother trying to fit more and won't hide the potentially allocatable space on the + * last (current_block_) block. TUNING: if we move to a mark scheme, revisit + * this code to keep allocation order intact. + */ + if (new_block->block_size == size) { + new_block->next = arena_head_; + arena_head_ = new_block; + } else { + int lost = (current_block_->block_size - current_block_->bytes_allocated); + lost_bytes_ += lost; + current_block_->next = new_block; + current_block_ = new_block; + } + allocation_block = new_block; + } + void* ptr = &allocation_block->ptr[allocation_block->bytes_allocated]; + allocation_block->bytes_allocated += size; + if (zero) { + memset(ptr, 0, size); + } + num_allocations_++; + return ptr; +} + +// Dump memory usage stats. +void ArenaAllocator::DumpMemStats(std::ostream& os) const { + size_t total = 0; + for (int i = 0; i < kNumAllocKinds; i++) { + total += alloc_stats_[i]; + } + os << " MEM: used: " << total << ", allocated: " << malloc_bytes_ + << ", lost: " << lost_bytes_ << "\n"; + os << "Number of blocks allocated: " << num_arena_blocks_ << ", Number of allocations: " + << num_allocations_ << ", avg: " << total / num_allocations_ << "\n"; + os << "===== Allocation by kind\n"; + for (int i = 0; i < kNumAllocKinds; i++) { + os << alloc_names[i] << std::setw(10) << alloc_stats_[i] << "\n"; + } +} + +} // namespace art diff --git a/src/compiler/dex/arena_allocator.h b/src/compiler/dex/arena_allocator.h new file mode 100644 index 0000000000000000000000000000000000000000..78d4614f902359b2a132b1572c6d1be770039cd8 --- /dev/null +++ b/src/compiler/dex/arena_allocator.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2013 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_SRC_COMPILER_DEX_COMPILER_ARENA_ALLOCATOR_H_ +#define ART_SRC_COMPILER_DEX_COMPILER_ARENA_ALLOCATOR_H_ + +#include +#include +#include "compiler_enums.h" + +namespace art { + +#define ARENA_DEFAULT_BLOCK_SIZE (256 * 1024) +#define ARENA_HIGH_WATER (16 * 1024) + +class ArenaAllocator { + public: + + // Type of allocation for memory tuning. + enum ArenaAllocKind { + kAllocMisc, + kAllocBB, + kAllocLIR, + kAllocMIR, + kAllocDFInfo, + kAllocGrowableArray, + kAllocGrowableBitMap, + kAllocDalvikToSSAMap, + kAllocDebugInfo, + kAllocSuccessor, + kAllocRegAlloc, + kAllocData, + kAllocPredecessors, + kNumAllocKinds + }; + + ArenaAllocator(size_t default_size = ARENA_DEFAULT_BLOCK_SIZE); + ~ArenaAllocator(); + void* NewMem(size_t size, bool zero, ArenaAllocKind kind); + size_t BytesAllocated() { + return malloc_bytes_; + } + + void DumpMemStats(std::ostream& os) const; + + private: + + // Variable-length allocation block. + struct ArenaMemBlock { + size_t block_size; + size_t bytes_allocated; + ArenaMemBlock *next; + char ptr[0]; + }; + + ArenaMemBlock* EmptyArenaBlock(); + + size_t default_size_; // Smallest size of new allocation block. + size_t block_size_; // Amount of allocatable bytes on a default block. + ArenaMemBlock* arena_head_; // Head of linked list of allocation blocks. + ArenaMemBlock* current_block_; // NOTE: code assumes there's always at least 1 block. + int num_arena_blocks_; + uint32_t malloc_bytes_; // Number of actual bytes malloc'd + uint32_t alloc_stats_[kNumAllocKinds]; // Bytes used by various allocation kinds. + uint32_t lost_bytes_; // Lost memory at end of too-small region + uint32_t num_allocations_; + +}; // ArenaAllocator + + +struct MemStats { + public: + void Dump(std::ostream& os) const { + arena_.DumpMemStats(os); + } + MemStats(const ArenaAllocator &arena) : arena_(arena){}; + private: + const ArenaAllocator &arena_; +}; // MemStats + +} // namespace art + +#endif // ART_SRC_COMPILER_DEX_COMPILER_ARENA_ALLOCATOR_H_ diff --git a/src/compiler/dex/arena_bit_vector.cc b/src/compiler/dex/arena_bit_vector.cc new file mode 100644 index 0000000000000000000000000000000000000000..6f664e5565819b37095f54aa39f5fea6ddb92e3f --- /dev/null +++ b/src/compiler/dex/arena_bit_vector.cc @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compiler_internals.h" +#include "dex_file-inl.h" + +namespace art { + +// TODO: profile to make sure this is still a win relative to just using shifted masks. +static uint32_t check_masks[32] = { + 0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, + 0x00000020, 0x00000040, 0x00000080, 0x00000100, 0x00000200, + 0x00000400, 0x00000800, 0x00001000, 0x00002000, 0x00004000, + 0x00008000, 0x00010000, 0x00020000, 0x00040000, 0x00080000, + 0x00100000, 0x00200000, 0x00400000, 0x00800000, 0x01000000, + 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, + 0x40000000, 0x80000000 }; + +ArenaBitVector::ArenaBitVector(ArenaAllocator* arena, unsigned int start_bits, + bool expandable, OatBitMapKind kind) + : arena_(arena), + expandable_(expandable), + kind_(kind), + storage_size_((start_bits + 31) >> 5), + storage_(static_cast(arena_->NewMem(storage_size_ * sizeof(uint32_t), true, + ArenaAllocator::kAllocGrowableBitMap))) { + DCHECK_EQ(sizeof(storage_[0]), 4U); // Assuming 32-bit units. +} + +/* + * Determine whether or not the specified bit is set. + */ +bool ArenaBitVector::IsBitSet(unsigned int num) { + DCHECK_LT(num, storage_size_ * sizeof(uint32_t) * 8); + + unsigned int val = storage_[num >> 5] & check_masks[num & 0x1f]; + return (val != 0); +} + +// Mark all bits bit as "clear". +void ArenaBitVector::ClearAllBits() { + memset(storage_, 0, storage_size_ * sizeof(uint32_t)); +} + +// Mark the specified bit as "set". +/* + * TUNING: this could have pathologically bad growth/expand behavior. Make sure we're + * not using it badly or change resize mechanism. + */ +void ArenaBitVector::SetBit(unsigned int num) { + if (num >= storage_size_ * sizeof(uint32_t) * 8) { + DCHECK(expandable_) << "Attempted to expand a non-expandable bitmap to position " << num; + + /* Round up to word boundaries for "num+1" bits */ + unsigned int new_size = (num + 1 + 31) >> 5; + DCHECK_GT(new_size, storage_size_); + uint32_t *new_storage = + static_cast(arena_->NewMem(new_size * sizeof(uint32_t), false, + ArenaAllocator::kAllocGrowableBitMap)); + memcpy(new_storage, storage_, storage_size_ * sizeof(uint32_t)); + // Zero out the new storage words. + memset(&new_storage[storage_size_], 0, (new_size - storage_size_) * sizeof(uint32_t)); + // TOTO: collect stats on space wasted because of resize. + storage_ = new_storage; + storage_size_ = new_size; + } + + storage_[num >> 5] |= check_masks[num & 0x1f]; +} + +// Mark the specified bit as "unset". +void ArenaBitVector::ClearBit(unsigned int num) { + DCHECK_LT(num, storage_size_ * sizeof(uint32_t) * 8); + storage_[num >> 5] &= ~check_masks[num & 0x1f]; +} + +// Copy a whole vector to the other. Sizes must match. +void ArenaBitVector::Copy(ArenaBitVector* src) { + DCHECK_EQ(storage_size_, src->GetStorageSize()); + memcpy(storage_, src->GetRawStorage(), sizeof(uint32_t) * storage_size_); +} + +// Intersect with another bit vector. Sizes and expandability must be the same. +void ArenaBitVector::Intersect(const ArenaBitVector* src) { + DCHECK_EQ(storage_size_, src->GetStorageSize()); + DCHECK_EQ(expandable_, src->IsExpandable()); + for (unsigned int idx = 0; idx < storage_size_; idx++) { + storage_[idx] &= src->GetRawStorageWord(idx); + } +} + +/* + * Union with another bit vector. Sizes and expandability must be the same. + */ +void ArenaBitVector::Union(const ArenaBitVector* src) { + DCHECK_EQ(storage_size_, src->GetStorageSize()); + DCHECK_EQ(expandable_, src->IsExpandable()); + for (unsigned int idx = 0; idx < storage_size_; idx++) { + storage_[idx] |= src->GetRawStorageWord(idx); + } +} + +// Are we equal to another bit vector? Note: expandability attributes must also match. +bool ArenaBitVector::Equal(const ArenaBitVector* src) { + if (storage_size_ != src->GetStorageSize() || + expandable_ != src->IsExpandable()) + return false; + + for (unsigned int idx = 0; idx < storage_size_; idx++) { + if (storage_[idx] != src->GetRawStorageWord(idx)) return false; + } + return true; +} + +// Count the number of bits that are set. +int ArenaBitVector::NumSetBits() +{ + unsigned int count = 0; + + for (unsigned int word = 0; word < storage_size_; word++) { + count += __builtin_popcount(storage_[word]); + } + return count; +} + +// Return the position of the next set bit. -1 means end-of-element reached. +// TUNING: Hot function. +int ArenaBitVector::Iterator::Next() +{ + // Did anything obviously change since we started? + DCHECK_EQ(bit_size_, p_bits_->GetStorageSize() * sizeof(uint32_t) * 8); + DCHECK_EQ(bit_storage_, p_bits_->GetRawStorage()); + + if (bit_index_ >= bit_size_) return -1; + + uint32_t word_index = bit_index_ >> 5; + uint32_t end_word_index = bit_size_ >> 5; + uint32_t word = bit_storage_[word_index++]; + + // Mask out any bits in the first word we've already considered. + word &= ~((1 << (bit_index_ & 0x1f))-1); + + for (; word_index <= end_word_index;) { + uint32_t bit_pos = bit_index_ & 0x1f; + if (word == 0) { + bit_index_ += (32 - bit_pos); + word = bit_storage_[word_index++]; + continue; + } + for (; bit_pos < 32; bit_pos++) { + if (word & (1 << bit_pos)) { + bit_index_++; + return bit_index_ - 1; + } + bit_index_++; + } + word = bit_storage_[word_index++]; + } + bit_index_ = bit_size_; + return -1; +} + +/* + * Mark specified number of bits as "set". Cannot set all bits like ClearAll + * since there might be unused bits - setting those to one will confuse the + * iterator. + */ +void ArenaBitVector::SetInitialBits(unsigned int num_bits) +{ + DCHECK_LE(((num_bits + 31) >> 5), storage_size_); + unsigned int idx; + for (idx = 0; idx < (num_bits >> 5); idx++) { + storage_[idx] = -1; + } + unsigned int rem_num_bits = num_bits & 0x1f; + if (rem_num_bits) { + storage_[idx] = (1 << rem_num_bits) - 1; + } +} + +} // namespace art diff --git a/src/compiler/dex/arena_bit_vector.h b/src/compiler/dex/arena_bit_vector.h new file mode 100644 index 0000000000000000000000000000000000000000..f5c471c5d356ba33e87be77bb55d966ec0d0855a --- /dev/null +++ b/src/compiler/dex/arena_bit_vector.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2013 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_SRC_COMPILER_DEX_COMPILER_ARENA_BIT_VECTOR_H_ +#define ART_SRC_COMPILER_DEX_COMPILER_ARENA_BIT_VECTOR_H_ + +#include +#include +#include "compiler_enums.h" +#include "arena_allocator.h" + +namespace art { + +/* + * Expanding bitmap, used for tracking resources. Bits are numbered starting + * from zero. All operations on a BitVector are unsynchronized. + */ +class ArenaBitVector { + public: + + class Iterator { + public: + Iterator(ArenaBitVector* bit_vector) + : p_bits_(bit_vector), + bit_storage_(bit_vector->GetRawStorage()), + bit_index_(0), + bit_size_(p_bits_->storage_size_ * sizeof(uint32_t) * 8) {}; + + int Next(); // Returns -1 when no next. + + static void* operator new(size_t size, ArenaAllocator* arena) { + return arena->NewMem(sizeof(ArenaBitVector::Iterator), true, + ArenaAllocator::kAllocGrowableBitMap); + }; + static void operator delete(void* p) {}; // Nop. + + private: + ArenaBitVector* const p_bits_; + uint32_t* const bit_storage_; + uint32_t bit_index_; // Current index (size in bits). + const uint32_t bit_size_; // Size of vector in bits. + }; + + ArenaBitVector(ArenaAllocator* arena, unsigned int start_bits, bool expandable, + OatBitMapKind kind = kBitMapMisc); + ~ArenaBitVector() {}; + + static void* operator new( size_t size, ArenaAllocator* arena) { + return arena->NewMem(sizeof(ArenaBitVector), true, ArenaAllocator::kAllocGrowableBitMap); + } + static void operator delete(void* p) {}; // Nop. + + void SetBit(unsigned int num); + void ClearBit(unsigned int num); + void MarkAllBits(bool set); + void DebugBitVector(char* msg, int length); + bool IsBitSet(unsigned int num); + void ClearAllBits(); + void SetInitialBits(unsigned int num_bits); + void Copy(ArenaBitVector* src); + void Intersect(const ArenaBitVector* src2); + void Union(const ArenaBitVector* src); + bool Equal(const ArenaBitVector* src); + int NumSetBits(); + + uint32_t GetStorageSize() const { return storage_size_; } + bool IsExpandable() const { return expandable_; } + uint32_t GetRawStorageWord(size_t idx) const { return storage_[idx]; } + uint32_t* GetRawStorage() { return storage_; } + + private: + ArenaAllocator* const arena_; + const bool expandable_; // expand bitmap if we run out? + const OatBitMapKind kind_; // for memory use tuning. + uint32_t storage_size_; // current size, in 32-bit words. + uint32_t* storage_; +}; + + +} // namespace art + +#endif // ART_SRC_COMPILER_DEX_COMPILER_ARENA_BIT_VECTOR_H_ diff --git a/src/compiler/dex/backend.h b/src/compiler/dex/backend.h new file mode 100644 index 0000000000000000000000000000000000000000..45a1531b8576f34f736ac0946b1262d3ebfba671 --- /dev/null +++ b/src/compiler/dex/backend.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2013 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_SRC_COMPILER_DEX_BACKEND_H_ +#define ART_SRC_COMPILER_DEX_BACKEND_H_ + +#include "compiled_method.h" +#include "arena_allocator.h" + +namespace art { + +class Backend { + + public: + virtual ~Backend() {}; + virtual void Materialize() = 0; + virtual CompiledMethod* GetCompiledMethod() = 0; + + protected: + Backend(ArenaAllocator* arena) : arena_(arena) {}; + ArenaAllocator* const arena_; + +}; // Class Backend + +} // namespace art + +#endif // ART_SRC_COMPILER_DEX_BACKEND_H_ diff --git a/src/compiler/dex/compiler_enums.h b/src/compiler/dex/compiler_enums.h new file mode 100644 index 0000000000000000000000000000000000000000..bc456b2e70e31cd0cb8636fdbfb8678c79a361a0 --- /dev/null +++ b/src/compiler/dex/compiler_enums.h @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_SRC_COMPILER_DEX_COMPILERENUMS_H_ +#define ART_SRC_COMPILER_DEX_COMPILERENUMS_H_ + +#include "dex_instruction.h" + +namespace art { + +enum RegisterClass { + kCoreReg, + kFPReg, + kAnyReg, +}; + +enum SpecialTargetRegister { + kSelf, // Thread pointer. + kSuspend, // Used to reduce suspend checks for some targets. + kLr, + kPc, + kSp, + kArg0, + kArg1, + kArg2, + kArg3, + kFArg0, + kFArg1, + kFArg2, + kFArg3, + kRet0, + kRet1, + kInvokeTgt, + kCount +}; + +enum RegLocationType { + kLocDalvikFrame = 0, // Normal Dalvik register + kLocPhysReg, + kLocCompilerTemp, + kLocInvalid +}; + +enum BBType { + kEntryBlock, + kDalvikByteCode, + kExitBlock, + kExceptionHandling, + kDead, +}; + +/* + * Def/Use encoding in 64-bit use_mask/def_mask. Low positions used for target-specific + * registers (and typically use the register number as the position). High positions + * reserved for common and abstract resources. + */ + +enum ResourceEncodingPos { + kMustNotAlias = 63, + kHeapRef = 62, // Default memory reference type. + kLiteral = 61, // Literal pool memory reference. + kDalvikReg = 60, // Dalvik v_reg memory reference. + kFPStatus = 59, + kCCode = 58, + kLowestCommonResource = kCCode +}; + +// Shared pseudo opcodes - must be < 0. +enum LIRPseudoOpcode { + kPseudoExportedPC = -16, + kPseudoSafepointPC = -15, + kPseudoIntrinsicRetry = -14, + kPseudoSuspendTarget = -13, + kPseudoThrowTarget = -12, + kPseudoCaseLabel = -11, + kPseudoMethodEntry = -10, + kPseudoMethodExit = -9, + kPseudoBarrier = -8, + kPseudoEntryBlock = -7, + kPseudoExitBlock = -6, + kPseudoTargetLabel = -5, + kPseudoDalvikByteCodeBoundary = -4, + kPseudoPseudoAlign4 = -3, + kPseudoEHBlockLabel = -2, + kPseudoNormalBlockLabel = -1, +}; + +enum ExtendedMIROpcode { + kMirOpFirst = kNumPackedOpcodes, + kMirOpPhi = kMirOpFirst, + kMirOpCopy, + kMirOpFusedCmplFloat, + kMirOpFusedCmpgFloat, + kMirOpFusedCmplDouble, + kMirOpFusedCmpgDouble, + kMirOpFusedCmpLong, + kMirOpNop, + kMirOpNullCheck, + kMirOpRangeCheck, + kMirOpDivZeroCheck, + kMirOpCheck, + kMirOpCheckPart2, + kMirOpSelect, + kMirOpLast, +}; + +enum MIROptimizationFlagPositons { + kMIRIgnoreNullCheck = 0, + kMIRNullCheckOnly, + kMIRIgnoreRangeCheck, + kMIRRangeCheckOnly, + kMIRInlined, // Invoke is inlined (ie dead). + kMIRInlinedPred, // Invoke is inlined via prediction. + kMIRCallee, // Instruction is inlined from callee. + kMIRIgnoreSuspendCheck, + kMIRDup, + kMIRMark, // Temporary node mark. +}; + +// For successor_block_list. +enum BlockListType { + kNotUsed = 0, + kCatch, + kPackedSwitch, + kSparseSwitch, +}; + +enum AssemblerStatus { + kSuccess, + kRetryAll, +}; + +enum OpSize { + kWord, + kLong, + kSingle, + kDouble, + kUnsignedHalf, + kSignedHalf, + kUnsignedByte, + kSignedByte, +}; + +std::ostream& operator<<(std::ostream& os, const OpSize& kind); + +enum OpKind { + kOpMov, + kOpMvn, + kOpCmp, + kOpLsl, + kOpLsr, + kOpAsr, + kOpRor, + kOpNot, + kOpAnd, + kOpOr, + kOpXor, + kOpNeg, + kOpAdd, + kOpAdc, + kOpSub, + kOpSbc, + kOpRsub, + kOpMul, + kOpDiv, + kOpRem, + kOpBic, + kOpCmn, + kOpTst, + kOpBkpt, + kOpBlx, + kOpPush, + kOpPop, + kOp2Char, + kOp2Short, + kOp2Byte, + kOpCondBr, + kOpUncondBr, + kOpBx, + kOpInvalid, +}; + +std::ostream& operator<<(std::ostream& os, const OpKind& kind); + +enum ConditionCode { + kCondEq, // equal + kCondNe, // not equal + kCondCs, // carry set (unsigned less than) + kCondUlt = kCondCs, + kCondCc, // carry clear (unsigned greater than or same) + kCondUge = kCondCc, + kCondMi, // minus + kCondPl, // plus, positive or zero + kCondVs, // overflow + kCondVc, // no overflow + kCondHi, // unsigned greater than + kCondLs, // unsigned lower or same + kCondGe, // signed greater than or equal + kCondLt, // signed less than + kCondGt, // signed greater than + kCondLe, // signed less than or equal + kCondAl, // always + kCondNv, // never +}; + +std::ostream& operator<<(std::ostream& os, const ConditionCode& kind); + +// Target specific condition encodings +enum ArmConditionCode { + kArmCondEq = 0x0, // 0000 + kArmCondNe = 0x1, // 0001 + kArmCondCs = 0x2, // 0010 + kArmCondCc = 0x3, // 0011 + kArmCondMi = 0x4, // 0100 + kArmCondPl = 0x5, // 0101 + kArmCondVs = 0x6, // 0110 + kArmCondVc = 0x7, // 0111 + kArmCondHi = 0x8, // 1000 + kArmCondLs = 0x9, // 1001 + kArmCondGe = 0xa, // 1010 + kArmCondLt = 0xb, // 1011 + kArmCondGt = 0xc, // 1100 + kArmCondLe = 0xd, // 1101 + kArmCondAl = 0xe, // 1110 + kArmCondNv = 0xf, // 1111 +}; + +std::ostream& operator<<(std::ostream& os, const ArmConditionCode& kind); + +enum X86ConditionCode { + kX86CondO = 0x0, // overflow + kX86CondNo = 0x1, // not overflow + + kX86CondB = 0x2, // below + kX86CondNae = kX86CondB, // not-above-equal + kX86CondC = kX86CondB, // carry + + kX86CondNb = 0x3, // not-below + kX86CondAe = kX86CondNb, // above-equal + kX86CondNc = kX86CondNb, // not-carry + + kX86CondZ = 0x4, // zero + kX86CondEq = kX86CondZ, // equal + + kX86CondNz = 0x5, // not-zero + kX86CondNe = kX86CondNz, // not-equal + + kX86CondBe = 0x6, // below-equal + kX86CondNa = kX86CondBe, // not-above + + kX86CondNbe = 0x7, // not-below-equal + kX86CondA = kX86CondNbe,// above + + kX86CondS = 0x8, // sign + kX86CondNs = 0x9, // not-sign + + kX86CondP = 0xa, // 8-bit parity even + kX86CondPE = kX86CondP, + + kX86CondNp = 0xb, // 8-bit parity odd + kX86CondPo = kX86CondNp, + + kX86CondL = 0xc, // less-than + kX86CondNge = kX86CondL, // not-greater-equal + + kX86CondNl = 0xd, // not-less-than + kX86CondGe = kX86CondNl, // not-greater-equal + + kX86CondLe = 0xe, // less-than-equal + kX86CondNg = kX86CondLe, // not-greater + + kX86CondNle = 0xf, // not-less-than + kX86CondG = kX86CondNle,// greater +}; + +std::ostream& operator<<(std::ostream& os, const X86ConditionCode& kind); + +enum ThrowKind { + kThrowNullPointer, + kThrowDivZero, + kThrowArrayBounds, + kThrowConstantArrayBounds, + kThrowNoSuchMethod, + kThrowStackOverflow, +}; + +enum SpecialCaseHandler { + kNoHandler, + kNullMethod, + kConstFunction, + kIGet, + kIGetBoolean, + kIGetObject, + kIGetByte, + kIGetChar, + kIGetShort, + kIGetWide, + kIPut, + kIPutBoolean, + kIPutObject, + kIPutByte, + kIPutChar, + kIPutShort, + kIPutWide, + kIdentity, +}; + +enum DividePattern { + DivideNone, + Divide3, + Divide5, + Divide7, +}; + +std::ostream& operator<<(std::ostream& os, const DividePattern& pattern); + +// Memory barrier types (see "The JSR-133 Cookbook for Compiler Writers"). +enum MemBarrierKind { + kLoadStore, + kLoadLoad, + kStoreStore, + kStoreLoad +}; + +std::ostream& operator<<(std::ostream& os, const MemBarrierKind& kind); + +enum OpFeatureFlags { + kIsBranch = 0, + kNoOperand, + kIsUnaryOp, + kIsBinaryOp, + kIsTertiaryOp, + kIsQuadOp, + kIsQuinOp, + kIsSextupleOp, + kIsIT, + kMemLoad, + kMemStore, + kPCRelFixup, // x86 FIXME: add NEEDS_FIXUP to instruction attributes. + kRegDef0, + kRegDef1, + kRegDefA, + kRegDefD, + kRegDefFPCSList0, + kRegDefFPCSList2, + kRegDefList0, + kRegDefList1, + kRegDefList2, + kRegDefLR, + kRegDefSP, + kRegUse0, + kRegUse1, + kRegUse2, + kRegUse3, + kRegUse4, + kRegUseA, + kRegUseC, + kRegUseD, + kRegUseFPCSList0, + kRegUseFPCSList2, + kRegUseList0, + kRegUseList1, + kRegUseLR, + kRegUsePC, + kRegUseSP, + kSetsCCodes, + kUsesCCodes +}; + +enum SelectInstructionKind { + kSelectNone, + kSelectConst, + kSelectMove, + kSelectGoto +}; + +std::ostream& operator<<(std::ostream& os, const SelectInstructionKind& kind); + +// Type of growable bitmap for memory tuning. +enum OatBitMapKind { + kBitMapMisc = 0, + kBitMapUse, + kBitMapDef, + kBitMapLiveIn, + kBitMapBMatrix, + kBitMapDominators, + kBitMapIDominated, + kBitMapDomFrontier, + kBitMapPhi, + kBitMapTmpBlocks, + kBitMapInputBlocks, + kBitMapRegisterV, + kBitMapTempSSARegisterV, + kBitMapNullCheck, + kBitMapTmpBlockV, + kBitMapPredecessors, + kNumBitMapKinds +}; + +std::ostream& operator<<(std::ostream& os, const OatBitMapKind& kind); + +} // namespace art + +#endif // ART_SRC_COMPILER_DEX_COMPILERENUMS_H_ diff --git a/src/compiler/dex/compiler_internals.h b/src/compiler/dex/compiler_internals.h new file mode 100644 index 0000000000000000000000000000000000000000..c680f1bc67bac81453b17f499b19cdc507dd2185 --- /dev/null +++ b/src/compiler/dex/compiler_internals.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2011 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_SRC_COMPILER_DEX_COMPILER_INTERNAL_H_ +#define ART_SRC_COMPILER_DEX_COMPILER_INTERNAL_H_ + +#include +#include +#include +#include + +#include "base/logging.h" +#include "class_linker.h" +#include "quick/mir_to_lir.h" +#include "compiler/driver/compiler_driver.h" +#include "mir_graph.h" +#include "compiler_ir.h" +#include "frontend.h" +#include "monitor.h" +#include "thread.h" +#include "utils.h" + +#endif // ART_SRC_COMPILER_DEX_COMPILER_INTERNAL_H_ diff --git a/src/compiler/dex/compiler_ir.h b/src/compiler/dex/compiler_ir.h new file mode 100644 index 0000000000000000000000000000000000000000..eb1aec187ab9923609a0b1ab3f2364f02ffb7c88 --- /dev/null +++ b/src/compiler/dex/compiler_ir.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2011 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_SRC_COMPILER_DEX_COMPILER_IR_H_ +#define ART_SRC_COMPILER_DEX_COMPILER_IR_H_ + +#include +#include +#include "compiler/dex/quick/mir_to_lir.h" +#include "backend.h" +#include "compiler/driver/compiler_driver.h" +#include "compiler/driver/dex_compilation_unit.h" +#include "compiler/llvm/intrinsic_helper.h" +#include "compiler/llvm/ir_builder.h" +#include "compiler_enums.h" +#include "dex_instruction.h" +#include "safe_map.h" +#include "arena_allocator.h" + +namespace art { + +class LLVMInfo; +namespace llvm { +class LlvmCompilationUnit; +} // namespace llvm + +struct ArenaMemBlock; +struct Memstats; +class MIRGraph; +class Mir2Lir; + +struct CompilationUnit { + CompilationUnit() + : compiler_driver(NULL), + class_linker(NULL), + dex_file(NULL), + class_loader(NULL), + class_def_idx(0), + method_idx(0), + code_item(NULL), + access_flags(0), + invoke_type(kDirect), + shorty(NULL), + disable_opt(0), + enable_debug(0), + verbose(false), + compiler_backend(kNoBackend), + instruction_set(kNone), + num_dalvik_registers(0), + insns(NULL), + num_ins(0), + num_outs(0), + num_regs(0), + num_compiler_temps(0), + compiler_flip_match(false), + mir_graph(NULL), + cg(NULL) {} + /* + * Fields needed/generated by common frontend and generally used throughout + * the compiler. + */ + CompilerDriver* compiler_driver; + ClassLinker* class_linker; // Linker to resolve fields and methods. + const DexFile* dex_file; // DexFile containing the method being compiled. + jobject class_loader; // compiling method's class loader. + uint32_t class_def_idx; // compiling method's defining class definition index. + uint32_t method_idx; // compiling method's index into method_ids of DexFile. + const DexFile::CodeItem* code_item; // compiling method's DexFile code_item. + uint32_t access_flags; // compiling method's access flags. + InvokeType invoke_type; // compiling method's invocation type. + const char* shorty; // compiling method's shorty. + uint32_t disable_opt; // opt_control_vector flags. + uint32_t enable_debug; // debugControlVector flags. + bool verbose; + CompilerBackend compiler_backend; + InstructionSet instruction_set; + + // TODO: much of this info available elsewhere. Go to the original source? + int num_dalvik_registers; // method->registers_size. + const uint16_t* insns; + int num_ins; + int num_outs; + int num_regs; // Unlike num_dalvik_registers, does not include ins. + + // TODO: may want to move this to MIRGraph. + int num_compiler_temps; + + // If non-empty, apply optimizer/debug flags only to matching methods. + std::string compiler_method_match; + // Flips sense of compiler_method_match - apply flags if doesn't match. + bool compiler_flip_match; + + // TODO: move memory management to mir_graph, or just switch to using standard containers. + ArenaAllocator arena; + + UniquePtr mir_graph; // MIR container. + UniquePtr cg; // Target-specific codegen. +}; + +} // namespace art + +#endif // ART_SRC_COMPILER_DEX_COMPILER_IR_H_ diff --git a/src/compiler/dex/dataflow_iterator.cc b/src/compiler/dex/dataflow_iterator.cc new file mode 100644 index 0000000000000000000000000000000000000000..bb5b96992582b06a1aff84ffc698dc330ee0eabe --- /dev/null +++ b/src/compiler/dex/dataflow_iterator.cc @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dataflow_iterator.h" + +namespace art { + + BasicBlock* DataflowIterator::NextBody(bool had_change) { + changed_ |= had_change; + BasicBlock* res = NULL; + if (reverse_) { + if (is_iterative_ && changed_ && (idx_ < 0)) { + idx_ = start_idx_; + changed_ = false; + } + if (idx_ >= 0) { + int bb_id = block_id_list_->Get(idx_--); + res = mir_graph_->GetBasicBlock(bb_id); + } + } else { + if (is_iterative_ && changed_ && (idx_ >= end_idx_)) { + idx_ = start_idx_; + changed_ = false; + } + if (idx_ < end_idx_) { + int bb_id = block_id_list_->Get(idx_++); + res = mir_graph_->GetBasicBlock(bb_id); + } + } + return res; + } + + // AllNodes uses the existing GrowableArray iterator, so use different NextBody(). + BasicBlock* AllNodesIterator::NextBody(bool had_change) { + changed_ |= had_change; + BasicBlock* res = NULL; + bool keep_looking = true; + while (keep_looking) { + res = all_nodes_iterator_->Next(); + if (is_iterative_ && changed_ && (res == NULL)) { + all_nodes_iterator_->Reset(); + changed_ = false; + } else if ((res == NULL) || (!res->hidden)) { + keep_looking = false; + } + } + return res; + } + +} // namespace art diff --git a/src/compiler/dex/dataflow_iterator.h b/src/compiler/dex/dataflow_iterator.h new file mode 100644 index 0000000000000000000000000000000000000000..a4b38bd80f03589e10f82036a4151db598575b6c --- /dev/null +++ b/src/compiler/dex/dataflow_iterator.h @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2013 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_SRC_COMPILER_DEX_DATAFLOW_ITERATOR_H_ +#define ART_SRC_COMPILER_DEX_DATAFLOW_ITERATOR_H_ + +#include "compiler_ir.h" +#include "mir_graph.h" + +namespace art { + + /* + * This class supports iterating over lists of basic blocks in various + * interesting orders. Note that for efficiency, the visit orders have been pre-computed. + * The order itself will not change during the iteration. However, for some uses, + * auxiliary data associated with the basic blocks may be changed during the iteration, + * necessitating another pass over the list. + * + * To support this usage, we have is_iterative_. If false, the iteration is a one-shot + * pass through the pre-computed list using Next(). If true, the caller must tell the + * iterator whether a change has been made that necessitates another pass. Use + * Next(had_change) for this. The general idea is that the iterative_ use case means + * that the iterator will keep repeating the full basic block list until a complete pass + * is made through it with no changes. Note that calling Next(true) does not affect + * the iteration order or short-curcuit the current pass - it simply tells the iterator + * that once it has finished walking through the block list it should reset and do another + * full pass through the list. + */ + class DataflowIterator { + public: + + virtual ~DataflowIterator(){} + + // Return the next BasicBlock* to visit. + BasicBlock* Next() { + DCHECK(!is_iterative_); + return NextBody(false); + } + + /* + * Return the next BasicBlock* to visit, and tell the iterator whether any change + * has occurred that requires another full pass over the block list. + */ + BasicBlock* Next(bool had_change) { + DCHECK(is_iterative_); + return NextBody(had_change); + } + + protected: + DataflowIterator(MIRGraph* mir_graph, bool is_iterative, int start_idx, int end_idx, + bool reverse) + : mir_graph_(mir_graph), + is_iterative_(is_iterative), + start_idx_(start_idx), + end_idx_(end_idx), + reverse_(reverse), + block_id_list_(NULL), + idx_(0), + changed_(false) {} + + virtual BasicBlock* NextBody(bool had_change); + + MIRGraph* const mir_graph_; + const bool is_iterative_; + const int start_idx_; + const int end_idx_; + const bool reverse_; + GrowableArray* block_id_list_; + int idx_; + bool changed_; + + }; // DataflowIterator + + class ReachableNodesIterator : public DataflowIterator { + public: + + ReachableNodesIterator(MIRGraph* mir_graph, bool is_iterative) + : DataflowIterator(mir_graph, is_iterative, 0, + mir_graph->GetNumReachableBlocks(), false) { + idx_ = start_idx_; + block_id_list_ = mir_graph->GetDfsOrder(); + } + }; + + class PreOrderDfsIterator : public DataflowIterator { + public: + + PreOrderDfsIterator(MIRGraph* mir_graph, bool is_iterative) + : DataflowIterator(mir_graph, is_iterative, 0, + mir_graph->GetNumReachableBlocks(), false) { + idx_ = start_idx_; + block_id_list_ = mir_graph->GetDfsOrder(); + } + }; + + class PostOrderDfsIterator : public DataflowIterator { + public: + + PostOrderDfsIterator(MIRGraph* mir_graph, bool is_iterative) + : DataflowIterator(mir_graph, is_iterative, 0, + mir_graph->GetNumReachableBlocks(), false) { + idx_ = start_idx_; + block_id_list_ = mir_graph->GetDfsPostOrder(); + } + }; + + class ReversePostOrderDfsIterator : public DataflowIterator { + public: + + ReversePostOrderDfsIterator(MIRGraph* mir_graph, bool is_iterative) + : DataflowIterator(mir_graph, is_iterative, + mir_graph->GetNumReachableBlocks() -1, 0, true) { + idx_ = start_idx_; + block_id_list_ = mir_graph->GetDfsPostOrder(); + } + }; + + class PostOrderDOMIterator : public DataflowIterator { + public: + + PostOrderDOMIterator(MIRGraph* mir_graph, bool is_iterative) + : DataflowIterator(mir_graph, is_iterative, 0, + mir_graph->GetNumReachableBlocks(), false) { + idx_ = start_idx_; + block_id_list_ = mir_graph->GetDomPostOrder(); + } + }; + + class AllNodesIterator : public DataflowIterator { + public: + + AllNodesIterator(MIRGraph* mir_graph, bool is_iterative) + : DataflowIterator(mir_graph, is_iterative, 0, 0, false) { + all_nodes_iterator_ = + new (mir_graph->GetArena()) GrowableArray::Iterator (mir_graph->GetBlockList()); + } + + virtual void Reset() { + all_nodes_iterator_->Reset(); + } + + virtual BasicBlock* NextBody(bool had_change); + + private: + GrowableArray::Iterator* all_nodes_iterator_; + }; + +} // namespace art + +#endif // ART_SRC_COMPILER_DEX_DATAFLOW_ITERATOR_H_ diff --git a/src/compiler/dex/frontend.cc b/src/compiler/dex/frontend.cc new file mode 100644 index 0000000000000000000000000000000000000000..ca751ab8496d26fa77124318c235699c06da9a9e --- /dev/null +++ b/src/compiler/dex/frontend.cc @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "compiler/driver/compiler_driver.h" +#include "compiler_internals.h" +#include "dataflow_iterator.h" +#if defined(ART_USE_PORTABLE_COMPILER) +#include "compiler/llvm/llvm_compilation_unit.h" +#include "compiler/dex/portable/mir_to_gbc.h" +#endif +#include "leb128.h" +#include "mirror/object.h" +#include "runtime.h" +#include "backend.h" +#include "base/logging.h" + +namespace { +#if !defined(ART_USE_PORTABLE_COMPILER) + pthread_once_t llvm_multi_init = PTHREAD_ONCE_INIT; +#endif + void InitializeLLVMForQuick() { + ::llvm::llvm_start_multithreaded(); + } +} + +namespace art { +namespace llvm { +::llvm::Module* makeLLVMModuleContents(::llvm::Module* module); +} + +LLVMInfo::LLVMInfo() { +#if !defined(ART_USE_PORTABLE_COMPILER) + pthread_once(&llvm_multi_init, InitializeLLVMForQuick); +#endif + // Create context, module, intrinsic helper & ir builder + llvm_context_.reset(new ::llvm::LLVMContext()); + llvm_module_ = new ::llvm::Module("art", *llvm_context_); + ::llvm::StructType::create(*llvm_context_, "JavaObject"); + art::llvm::makeLLVMModuleContents(llvm_module_); + intrinsic_helper_.reset( new art::llvm::IntrinsicHelper(*llvm_context_, *llvm_module_)); + ir_builder_.reset(new art::llvm::IRBuilder(*llvm_context_, *llvm_module_, *intrinsic_helper_)); +} + +LLVMInfo::~LLVMInfo() { +} + +extern "C" void ArtInitQuickCompilerContext(art::CompilerDriver& compiler) { + CHECK(compiler.GetCompilerContext() == NULL); + LLVMInfo* llvm_info = new LLVMInfo(); + compiler.SetCompilerContext(llvm_info); +} + +extern "C" void ArtUnInitQuickCompilerContext(art::CompilerDriver& compiler) { + delete reinterpret_cast(compiler.GetCompilerContext()); + compiler.SetCompilerContext(NULL); +} + +/* Default optimizer/debug setting for the compiler. */ +static uint32_t kCompilerOptimizerDisableFlags = 0 | // Disable specific optimizations + (1 << kLoadStoreElimination) | + //(1 << kLoadHoisting) | + //(1 << kSuppressLoads) | + //(1 << kNullCheckElimination) | + //(1 << kPromoteRegs) | + //(1 << kTrackLiveTemps) | + //(1 << kSafeOptimizations) | + //(1 << kBBOpt) | + //(1 << kMatch) | + //(1 << kPromoteCompilerTemps) | + 0; + +static uint32_t kCompilerDebugFlags = 0 | // Enable debug/testing modes + //(1 << kDebugDisplayMissingTargets) | + //(1 << kDebugVerbose) | + //(1 << kDebugDumpCFG) | + //(1 << kDebugSlowFieldPath) | + //(1 << kDebugSlowInvokePath) | + //(1 << kDebugSlowStringPath) | + //(1 << kDebugSlowestFieldPath) | + //(1 << kDebugSlowestStringPath) | + //(1 << kDebugExerciseResolveMethod) | + //(1 << kDebugVerifyDataflow) | + //(1 << kDebugShowMemoryUsage) | + //(1 << kDebugShowNops) | + //(1 << kDebugCountOpcodes) | + //(1 << kDebugDumpCheckStats) | + //(1 << kDebugDumpBitcodeFile) | + //(1 << kDebugVerifyBitcode) | + //(1 << kDebugShowSummaryMemoryUsage) | + 0; + +static CompiledMethod* CompileMethod(CompilerDriver& compiler, + const CompilerBackend compiler_backend, + const DexFile::CodeItem* code_item, + uint32_t access_flags, InvokeType invoke_type, + uint32_t class_def_idx, uint32_t method_idx, + jobject class_loader, const DexFile& dex_file +#if defined(ART_USE_PORTABLE_COMPILER) + , llvm::LlvmCompilationUnit* llvm_compilation_unit +#endif +) +{ + VLOG(compiler) << "Compiling " << PrettyMethod(method_idx, dex_file) << "..."; + + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + UniquePtr cu(new CompilationUnit); + + cu->compiler_driver = &compiler; + cu->class_linker = class_linker; + cu->instruction_set = compiler.GetInstructionSet(); + cu->compiler_backend = compiler_backend; + DCHECK((cu->instruction_set == kThumb2) || + (cu->instruction_set == kX86) || + (cu->instruction_set == kMips)); + + + /* Adjust this value accordingly once inlining is performed */ + cu->num_dalvik_registers = code_item->registers_size_; + // TODO: set this from command line + cu->compiler_flip_match = false; + bool use_match = !cu->compiler_method_match.empty(); + bool match = use_match && (cu->compiler_flip_match ^ + (PrettyMethod(method_idx, dex_file).find(cu->compiler_method_match) != + std::string::npos)); + if (!use_match || match) { + cu->disable_opt = kCompilerOptimizerDisableFlags; + cu->enable_debug = kCompilerDebugFlags; + cu->verbose = VLOG_IS_ON(compiler) || + (cu->enable_debug & (1 << kDebugVerbose)); + } + + /* + * TODO: rework handling of optimization and debug flags. Should we split out + * MIR and backend flags? Need command-line setting as well. + */ + + if (compiler_backend == kPortable) { + // Fused long branches not currently usseful in bitcode. + cu->disable_opt |= (1 << kBranchFusing); + } + + if (cu->instruction_set == kMips) { + // Disable some optimizations for mips for now + cu->disable_opt |= ( + (1 << kLoadStoreElimination) | + (1 << kLoadHoisting) | + (1 << kSuppressLoads) | + (1 << kNullCheckElimination) | + (1 << kPromoteRegs) | + (1 << kTrackLiveTemps) | + (1 << kSafeOptimizations) | + (1 << kBBOpt) | + (1 << kMatch) | + (1 << kPromoteCompilerTemps)); + } + + cu->mir_graph.reset(new MIRGraph(cu.get(), &cu->arena)); + + /* Gathering opcode stats? */ + if (kCompilerDebugFlags & (1 << kDebugCountOpcodes)) { + cu->mir_graph->EnableOpcodeCounting(); + } + + /* Build the raw MIR graph */ + cu->mir_graph->InlineMethod(code_item, access_flags, invoke_type, class_def_idx, method_idx, + class_loader, dex_file); + + /* Do a code layout pass */ + cu->mir_graph->CodeLayout(); + + /* Perform SSA transformation for the whole method */ + cu->mir_graph->SSATransformation(); + + /* Do constant propagation */ + cu->mir_graph->PropagateConstants(); + + /* Count uses */ + cu->mir_graph->MethodUseCount(); + + /* Perform null check elimination */ + cu->mir_graph->NullCheckElimination(); + + /* Combine basic blocks where possible */ + cu->mir_graph->BasicBlockCombine(); + + /* Do some basic block optimizations */ + cu->mir_graph->BasicBlockOptimization(); + + if (cu->enable_debug & (1 << kDebugDumpCheckStats)) { + cu->mir_graph->DumpCheckStats(); + } + + if (kCompilerDebugFlags & (1 << kDebugCountOpcodes)) { + cu->mir_graph->ShowOpcodeStats(); + } + + /* Set up regLocation[] array to describe values - one for each ssa_name. */ + cu->mir_graph->BuildRegLocations(); + + CompiledMethod* result = NULL; + +#if defined(ART_USE_PORTABLE_COMPILER) + if (compiler_backend == kPortable) { + cu->cg.reset(PortableCodeGenerator(cu.get(), cu->mir_graph.get(), &cu->arena, + llvm_compilation_unit)); + } else +#endif + { + switch (compiler.GetInstructionSet()) { + case kThumb2: + cu->cg.reset(ArmCodeGenerator(cu.get(), cu->mir_graph.get(), &cu->arena)); break; + case kMips: + cu->cg.reset(MipsCodeGenerator(cu.get(), cu->mir_graph.get(), &cu->arena)); break; + case kX86: + cu->cg.reset(X86CodeGenerator(cu.get(), cu->mir_graph.get(), &cu->arena)); break; + default: + LOG(FATAL) << "Unexpected instruction set: " << compiler.GetInstructionSet(); + } + } + + cu->cg->Materialize(); + + result = cu->cg->GetCompiledMethod(); + + if (result) { + VLOG(compiler) << "Compiled " << PrettyMethod(method_idx, dex_file); + } else { + VLOG(compiler) << "Deferred " << PrettyMethod(method_idx, dex_file); + } + + if (cu->enable_debug & (1 << kDebugShowMemoryUsage)) { + if (cu->arena.BytesAllocated() > (5 * 1024 *1024)) { + MemStats mem_stats(cu->arena); + LOG(INFO) << PrettyMethod(method_idx, dex_file) << " " << Dumpable(mem_stats); + } + } + + if (cu->enable_debug & (1 << kDebugShowSummaryMemoryUsage)) { + LOG(INFO) << "MEMINFO " << cu->arena.BytesAllocated() << " " << cu->mir_graph->GetNumBlocks() + << " " << PrettyMethod(method_idx, dex_file); + } + + return result; +} + +CompiledMethod* CompileOneMethod(CompilerDriver& compiler, + const CompilerBackend backend, + const DexFile::CodeItem* code_item, + uint32_t access_flags, + InvokeType invoke_type, + uint32_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const DexFile& dex_file, + llvm::LlvmCompilationUnit* llvm_compilation_unit) +{ + return CompileMethod(compiler, backend, code_item, access_flags, invoke_type, class_def_idx, + method_idx, class_loader, dex_file +#if defined(ART_USE_PORTABLE_COMPILER) + , llvm_compilation_unit +#endif + ); +} + +} // namespace art + +extern "C" art::CompiledMethod* + ArtQuickCompileMethod(art::CompilerDriver& compiler, + const art::DexFile::CodeItem* code_item, + uint32_t access_flags, art::InvokeType invoke_type, + uint32_t class_def_idx, uint32_t method_idx, jobject class_loader, + const art::DexFile& dex_file) +{ + // TODO: check method fingerprint here to determine appropriate backend type. Until then, use build default + art::CompilerBackend backend = compiler.GetCompilerBackend(); + return art::CompileOneMethod(compiler, backend, code_item, access_flags, invoke_type, + class_def_idx, method_idx, class_loader, dex_file, + NULL /* use thread llvm_info */); +} diff --git a/src/compiler/dex/frontend.h b/src/compiler/dex/frontend.h new file mode 100644 index 0000000000000000000000000000000000000000..dc57a23485f5580c678232e51b038426e4e9f923 --- /dev/null +++ b/src/compiler/dex/frontend.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2011 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_SRC_COMPILER_DEX_COMPILER_H_ +#define ART_SRC_COMPILER_DEX_COMPILER_H_ + +#include "dex_file.h" +#include "dex_instruction.h" + +namespace llvm { + class Module; + class LLVMContext; +} + +namespace art { +namespace llvm { + class IntrinsicHelper; + class IRBuilder; +} + +/* + * Assembly is an iterative process, and usually terminates within + * two or three passes. This should be high enough to handle bizarre + * cases, but detect an infinite loop bug. + */ +#define MAX_ASSEMBLER_RETRIES 50 + +// Suppress optimization if corresponding bit set. +enum opt_control_vector { + kLoadStoreElimination = 0, + kLoadHoisting, + kSuppressLoads, + kNullCheckElimination, + kPromoteRegs, + kTrackLiveTemps, + kSafeOptimizations, + kBBOpt, + kMatch, + kPromoteCompilerTemps, + kBranchFusing, +}; + +// Force code generation paths for testing. +enum debugControlVector { + kDebugVerbose, + kDebugDumpCFG, + kDebugSlowFieldPath, + kDebugSlowInvokePath, + kDebugSlowStringPath, + kDebugSlowTypePath, + kDebugSlowestFieldPath, + kDebugSlowestStringPath, + kDebugExerciseResolveMethod, + kDebugVerifyDataflow, + kDebugShowMemoryUsage, + kDebugShowNops, + kDebugCountOpcodes, + kDebugDumpCheckStats, + kDebugDumpBitcodeFile, + kDebugVerifyBitcode, + kDebugShowSummaryMemoryUsage, +}; + +class LLVMInfo { + public: + LLVMInfo(); + ~LLVMInfo(); + + ::llvm::LLVMContext* GetLLVMContext() { + return llvm_context_.get(); + } + + ::llvm::Module* GetLLVMModule() { + return llvm_module_; + } + + art::llvm::IntrinsicHelper* GetIntrinsicHelper() { + return intrinsic_helper_.get(); + } + + art::llvm::IRBuilder* GetIRBuilder() { + return ir_builder_.get(); + } + + private: + UniquePtr< ::llvm::LLVMContext> llvm_context_; + ::llvm::Module* llvm_module_; // Managed by context_. + UniquePtr intrinsic_helper_; + UniquePtr ir_builder_; +}; + +struct CompilationUnit; +struct BasicBlock; + +} // namespace art + +extern "C" art::CompiledMethod* ArtCompileMethod(art::CompilerDriver& driver, + const art::DexFile::CodeItem* code_item, + uint32_t access_flags, + art::InvokeType invoke_type, + uint32_t class_dex_idx, + uint32_t method_idx, + jobject class_loader, + const art::DexFile& dex_file); + +#endif // ART_SRC_COMPILER_DEX_COMPILER_H_ diff --git a/src/compiler/dex/growable_array.h b/src/compiler/dex/growable_array.h new file mode 100644 index 0000000000000000000000000000000000000000..c4684a71f665d70f3ae34741192fa12ec5d9e895 --- /dev/null +++ b/src/compiler/dex/growable_array.h @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2013 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_SRC_COMPILER_DEX_GROWABLE_LIST_H_ +#define ART_SRC_COMPILER_DEX_GROWABLE_LIST_H_ + +#include +#include +#include "compiler_enums.h" +#include "arena_allocator.h" + +namespace art { + +struct CompilationUnit; + +// Type of growable list for memory tuning. +enum OatListKind { + kGrowableArrayMisc = 0, + kGrowableArrayBlockList, + kGrowableArraySSAtoDalvikMap, + kGrowableArrayDfsOrder, + kGrowableArrayDfsPostOrder, + kGrowableArrayDomPostOrderTraversal, + kGrowableArrayThrowLaunchPads, + kGrowableArraySuspendLaunchPads, + kGrowableArraySwitchTables, + kGrowableArrayFillArrayData, + kGrowableArraySuccessorBlocks, + kGrowableArrayPredecessors, + kGNumListKinds +}; + +template +class GrowableArray { + public: + + class Iterator { + public: + Iterator(GrowableArray* g_list) + : idx_(0), + g_list_(g_list) {}; + + // NOTE: returns 0/NULL when no next. + // TODO: redo to make usage consistent with other iterators. + T Next() { + if (idx_ >= g_list_->Size()) { + return 0; + } else { + return g_list_->Get(idx_++); + } + } + + void Reset() { + idx_ = 0; + } + + static void* operator new(size_t size, ArenaAllocator* arena) { + return arena->NewMem(sizeof(GrowableArray::Iterator), true, ArenaAllocator::kAllocGrowableArray); + }; + static void operator delete(void* p) {}; // Nop. + + private: + size_t idx_; + GrowableArray* const g_list_; + }; + + GrowableArray(ArenaAllocator* arena, size_t init_length, OatListKind kind = kGrowableArrayMisc) + : arena_(arena), + num_allocated_(init_length), + num_used_(0), + kind_(kind) { + elem_list_ = static_cast(arena_->NewMem(sizeof(T) * init_length, true, + ArenaAllocator::kAllocGrowableArray)); + }; + + + // Expand the list size to at least new length. + void Resize(size_t new_length) { + if (new_length <= num_allocated_) return; + // If it's a small list double the size, else grow 1.5x. + size_t target_length = + (num_allocated_ < 128) ? num_allocated_ << 1 : num_allocated_ + (num_allocated_ >> 1); + if (new_length > target_length) { + target_length = new_length; + } + T* new_array = static_cast(arena_->NewMem(sizeof(T) * target_length, true, + ArenaAllocator::kAllocGrowableArray)); + memcpy(new_array, elem_list_, sizeof(T) * num_allocated_); + num_allocated_ = target_length; + elem_list_ = new_array; + }; + + // NOTE: does not return storage, just resets use count. + void Reset() { + num_used_ = 0; + } + + // Insert an element to the end of a list, resizing if necessary. + void Insert(T elem) { + if (num_used_ == num_allocated_) { + Resize(num_used_ + 1); + } + elem_list_[num_used_++] = elem; + }; + + T Get(size_t index) const { + DCHECK_LT(index, num_used_); + return elem_list_[index]; + }; + + // Overwrite existing element at position index. List must be large enough. + void Put(size_t index, T elem) { + DCHECK_LT(index, num_used_); + elem_list_[index] = elem; + } + + void Increment(size_t index) { + DCHECK_LT(index, num_used_); + elem_list_[index]++; + } + + void Delete(T element) { + bool found = false; + for (size_t i = 0; i < num_used_ - 1; i++) { + if (!found && elem_list_[i] == element) { + found = true; + } + if (found) { + elem_list_[i] = elem_list_[i+1]; + } + } + // We should either have found the element, or it was the last (unscanned) element. + DCHECK(found || (element == elem_list_[num_used_ - 1])); + num_used_--; + }; + + size_t GetNumAllocated() const { return num_allocated_; } + + size_t Size() const { return num_used_; } + + T* GetRawStorage() const { return elem_list_; } + + static void* operator new(size_t size, ArenaAllocator* arena) { + return arena->NewMem(sizeof(GrowableArray), true, ArenaAllocator::kAllocGrowableArray); + }; + static void operator delete(void* p) {}; // Nop. + + private: + ArenaAllocator* const arena_; + size_t num_allocated_; + size_t num_used_; + OatListKind kind_; + T* elem_list_; +}; + +} // namespace art + +#endif // ART_SRC_COMPILER_DEX_GROWABLE_LIST_H_ diff --git a/src/compiler/dex/local_value_numbering.cc b/src/compiler/dex/local_value_numbering.cc new file mode 100644 index 0000000000000000000000000000000000000000..ec5ab5db38509e381257e32dfd45c60ab0bf9a99 --- /dev/null +++ b/src/compiler/dex/local_value_numbering.cc @@ -0,0 +1,518 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "local_value_numbering.h" + +namespace art { + + +uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) +{ + uint16_t res = NO_VALUE; + uint16_t opcode = mir->dalvikInsn.opcode; + switch (opcode) { + case Instruction::NOP: + case Instruction::RETURN_VOID: + case Instruction::RETURN: + case Instruction::RETURN_OBJECT: + case Instruction::RETURN_WIDE: + case Instruction::MONITOR_ENTER: + case Instruction::MONITOR_EXIT: + case Instruction::GOTO: + case Instruction::GOTO_16: + case Instruction::GOTO_32: + case Instruction::CHECK_CAST: + case Instruction::THROW: + case Instruction::FILL_ARRAY_DATA: + case Instruction::FILLED_NEW_ARRAY: + case Instruction::FILLED_NEW_ARRAY_RANGE: + case Instruction::PACKED_SWITCH: + case Instruction::SPARSE_SWITCH: + case Instruction::IF_EQ: + case Instruction::IF_NE: + case Instruction::IF_LT: + case Instruction::IF_GE: + case Instruction::IF_GT: + case Instruction::IF_LE: + case Instruction::IF_EQZ: + case Instruction::IF_NEZ: + case Instruction::IF_LTZ: + case Instruction::IF_GEZ: + case Instruction::IF_GTZ: + case Instruction::IF_LEZ: + case Instruction::INVOKE_STATIC_RANGE: + case Instruction::INVOKE_STATIC: + case Instruction::INVOKE_DIRECT: + case Instruction::INVOKE_DIRECT_RANGE: + case Instruction::INVOKE_VIRTUAL: + case Instruction::INVOKE_VIRTUAL_RANGE: + case Instruction::INVOKE_SUPER: + case Instruction::INVOKE_SUPER_RANGE: + case Instruction::INVOKE_INTERFACE: + case Instruction::INVOKE_INTERFACE_RANGE: + case kMirOpFusedCmplFloat: + case kMirOpFusedCmpgFloat: + case kMirOpFusedCmplDouble: + case kMirOpFusedCmpgDouble: + case kMirOpFusedCmpLong: + // Nothing defined - take no action. + break; + + case Instruction::MOVE_EXCEPTION: + case Instruction::MOVE_RESULT: + case Instruction::MOVE_RESULT_OBJECT: + case Instruction::INSTANCE_OF: + case Instruction::NEW_INSTANCE: + case Instruction::CONST_STRING: + case Instruction::CONST_STRING_JUMBO: + case Instruction::CONST_CLASS: + case Instruction::NEW_ARRAY: { + // 1 result, treat as unique each time, use result s_reg - will be unique. + uint16_t res = GetOperandValue(mir->ssa_rep->defs[0]); + SetOperandValue(mir->ssa_rep->defs[0], res); + } + break; + case Instruction::MOVE_RESULT_WIDE: { + // 1 wide result, treat as unique each time, use result s_reg - will be unique. + uint16_t res = GetOperandValueWide(mir->ssa_rep->defs[0]); + SetOperandValueWide(mir->ssa_rep->defs[0], res); + } + break; + + case kMirOpPhi: + /* + * Because we'll only see phi nodes at the beginning of an extended basic block, + * we can ignore them. Revisit if we shift to global value numbering. + */ + break; + + case Instruction::MOVE: + case Instruction::MOVE_OBJECT: + case Instruction::MOVE_16: + case Instruction::MOVE_OBJECT_16: + case Instruction::MOVE_FROM16: + case Instruction::MOVE_OBJECT_FROM16: + case kMirOpCopy: { + // Just copy value number of source to value number of resulit. + uint16_t res = GetOperandValue(mir->ssa_rep->uses[0]); + SetOperandValue(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::MOVE_WIDE: + case Instruction::MOVE_WIDE_16: + case Instruction::MOVE_WIDE_FROM16: { + // Just copy value number of source to value number of result. + uint16_t res = GetOperandValueWide(mir->ssa_rep->uses[0]); + SetOperandValueWide(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::CONST: + case Instruction::CONST_4: + case Instruction::CONST_16: { + uint16_t res = LookupValue(Instruction::CONST, Low16Bits(mir->dalvikInsn.vB), + High16Bits(mir->dalvikInsn.vB >> 16), 0); + SetOperandValue(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::CONST_HIGH16: { + uint16_t res = LookupValue(Instruction::CONST, 0, mir->dalvikInsn.vB, 0); + SetOperandValue(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::CONST_WIDE_16: + case Instruction::CONST_WIDE_32: { + uint16_t low_res = LookupValue(Instruction::CONST, Low16Bits(mir->dalvikInsn.vB), + High16Bits(mir->dalvikInsn.vB >> 16), 1); + uint16_t high_res; + if (mir->dalvikInsn.vB & 0x80000000) { + high_res = LookupValue(Instruction::CONST, 0xffff, 0xffff, 2); + } else { + high_res = LookupValue(Instruction::CONST, 0, 0, 2); + } + uint16_t res = LookupValue(Instruction::CONST, low_res, high_res, 3); + SetOperandValue(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::CONST_WIDE: { + uint32_t low_word = Low32Bits(mir->dalvikInsn.vB_wide); + uint32_t high_word = High32Bits(mir->dalvikInsn.vB_wide); + uint16_t low_res = LookupValue(Instruction::CONST, Low16Bits(low_word), + High16Bits(low_word), 1); + uint16_t high_res = LookupValue(Instruction::CONST, Low16Bits(high_word), + High16Bits(high_word), 2); + uint16_t res = LookupValue(Instruction::CONST, low_res, high_res, 3); + SetOperandValueWide(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::CONST_WIDE_HIGH16: { + uint16_t low_res = LookupValue(Instruction::CONST, 0, 0, 1); + uint16_t high_res = LookupValue(Instruction::CONST, 0, Low16Bits(mir->dalvikInsn.vB), 2); + uint16_t res = LookupValue(Instruction::CONST, low_res, high_res, 3); + SetOperandValueWide(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::ARRAY_LENGTH: + case Instruction::NEG_INT: + case Instruction::NOT_INT: + case Instruction::NEG_FLOAT: + case Instruction::INT_TO_BYTE: + case Instruction::INT_TO_SHORT: + case Instruction::INT_TO_CHAR: + case Instruction::INT_TO_FLOAT: + case Instruction::FLOAT_TO_INT: { + // res = op + 1 operand + uint16_t operand1 = GetOperandValue(mir->ssa_rep->uses[0]); + uint16_t res = LookupValue(opcode, operand1, NO_VALUE, NO_VALUE); + SetOperandValue(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::LONG_TO_FLOAT: + case Instruction::LONG_TO_INT: + case Instruction::DOUBLE_TO_FLOAT: + case Instruction::DOUBLE_TO_INT: { + // res = op + 1 wide operand + uint16_t operand1 = GetOperandValue(mir->ssa_rep->uses[0]); + uint16_t res = LookupValue(opcode, operand1, NO_VALUE, NO_VALUE); + SetOperandValue(mir->ssa_rep->defs[0], res); + } + break; + + + case Instruction::DOUBLE_TO_LONG: + case Instruction::LONG_TO_DOUBLE: + case Instruction::NEG_LONG: + case Instruction::NOT_LONG: + case Instruction::NEG_DOUBLE: { + // wide res = op + 1 wide operand + uint16_t operand1 = GetOperandValueWide(mir->ssa_rep->uses[0]); + uint16_t res = LookupValue(opcode, operand1, NO_VALUE, NO_VALUE); + SetOperandValueWide(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::FLOAT_TO_DOUBLE: + case Instruction::FLOAT_TO_LONG: + case Instruction::INT_TO_DOUBLE: + case Instruction::INT_TO_LONG: { + // wide res = op + 1 operand + uint16_t operand1 = GetOperandValueWide(mir->ssa_rep->uses[0]); + uint16_t res = LookupValue(opcode, operand1, NO_VALUE, NO_VALUE); + SetOperandValueWide(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::CMPL_DOUBLE: + case Instruction::CMPG_DOUBLE: + case Instruction::CMP_LONG: { + // res = op + 2 wide operands + uint16_t operand1 = GetOperandValueWide(mir->ssa_rep->uses[0]); + uint16_t operand2 = GetOperandValueWide(mir->ssa_rep->uses[2]); + uint16_t res = LookupValue(opcode, operand1, operand2, NO_VALUE); + SetOperandValue(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::CMPG_FLOAT: + case Instruction::CMPL_FLOAT: + case Instruction::ADD_INT: + case Instruction::ADD_INT_2ADDR: + case Instruction::MUL_INT: + case Instruction::MUL_INT_2ADDR: + case Instruction::AND_INT: + case Instruction::AND_INT_2ADDR: + case Instruction::OR_INT: + case Instruction::OR_INT_2ADDR: + case Instruction::XOR_INT: + case Instruction::XOR_INT_2ADDR: + case Instruction::SUB_INT: + case Instruction::SUB_INT_2ADDR: + case Instruction::DIV_INT: + case Instruction::DIV_INT_2ADDR: + case Instruction::REM_INT: + case Instruction::REM_INT_2ADDR: + case Instruction::SHL_INT: + case Instruction::SHL_INT_2ADDR: + case Instruction::SHR_INT: + case Instruction::SHR_INT_2ADDR: + case Instruction::USHR_INT: + case Instruction::USHR_INT_2ADDR: { + // res = op + 2 operands + uint16_t operand1 = GetOperandValue(mir->ssa_rep->uses[0]); + uint16_t operand2 = GetOperandValue(mir->ssa_rep->uses[1]); + uint16_t res = LookupValue(opcode, operand1, operand2, NO_VALUE); + SetOperandValue(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::ADD_LONG: + case Instruction::SUB_LONG: + case Instruction::MUL_LONG: + case Instruction::DIV_LONG: + case Instruction::REM_LONG: + case Instruction::AND_LONG: + case Instruction::OR_LONG: + case Instruction::XOR_LONG: + case Instruction::ADD_LONG_2ADDR: + case Instruction::SUB_LONG_2ADDR: + case Instruction::MUL_LONG_2ADDR: + case Instruction::DIV_LONG_2ADDR: + case Instruction::REM_LONG_2ADDR: + case Instruction::AND_LONG_2ADDR: + case Instruction::OR_LONG_2ADDR: + case Instruction::XOR_LONG_2ADDR: + case Instruction::ADD_DOUBLE: + case Instruction::SUB_DOUBLE: + case Instruction::MUL_DOUBLE: + case Instruction::DIV_DOUBLE: + case Instruction::REM_DOUBLE: + case Instruction::ADD_DOUBLE_2ADDR: + case Instruction::SUB_DOUBLE_2ADDR: + case Instruction::MUL_DOUBLE_2ADDR: + case Instruction::DIV_DOUBLE_2ADDR: + case Instruction::REM_DOUBLE_2ADDR: { + // wide res = op + 2 wide operands + uint16_t operand1 = GetOperandValueWide(mir->ssa_rep->uses[0]); + uint16_t operand2 = GetOperandValueWide(mir->ssa_rep->uses[2]); + uint16_t res = LookupValue(opcode, operand1, operand2, NO_VALUE); + SetOperandValueWide(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::SHL_LONG: + case Instruction::SHR_LONG: + case Instruction::USHR_LONG: + case Instruction::SHL_LONG_2ADDR: + case Instruction::SHR_LONG_2ADDR: + case Instruction::USHR_LONG_2ADDR: { + // wide res = op + 1 wide operand + 1 operand + uint16_t operand1 = GetOperandValueWide(mir->ssa_rep->uses[0]); + uint16_t operand2 = GetOperandValueWide(mir->ssa_rep->uses[2]); + uint16_t res = LookupValue(opcode, operand1, operand2, NO_VALUE); + SetOperandValueWide(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::ADD_FLOAT: + case Instruction::SUB_FLOAT: + case Instruction::MUL_FLOAT: + case Instruction::DIV_FLOAT: + case Instruction::REM_FLOAT: + case Instruction::ADD_FLOAT_2ADDR: + case Instruction::SUB_FLOAT_2ADDR: + case Instruction::MUL_FLOAT_2ADDR: + case Instruction::DIV_FLOAT_2ADDR: + case Instruction::REM_FLOAT_2ADDR: { + // res = op + 2 operands + uint16_t operand1 = GetOperandValue(mir->ssa_rep->uses[0]); + uint16_t operand2 = GetOperandValue(mir->ssa_rep->uses[1]); + uint16_t res = LookupValue(opcode, operand1, operand2, NO_VALUE); + SetOperandValue(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::RSUB_INT: + case Instruction::ADD_INT_LIT16: + case Instruction::MUL_INT_LIT16: + case Instruction::DIV_INT_LIT16: + case Instruction::REM_INT_LIT16: + case Instruction::AND_INT_LIT16: + case Instruction::OR_INT_LIT16: + case Instruction::XOR_INT_LIT16: + case Instruction::ADD_INT_LIT8: + case Instruction::RSUB_INT_LIT8: + case Instruction::MUL_INT_LIT8: + case Instruction::DIV_INT_LIT8: + case Instruction::REM_INT_LIT8: + case Instruction::AND_INT_LIT8: + case Instruction::OR_INT_LIT8: + case Instruction::XOR_INT_LIT8: + case Instruction::SHL_INT_LIT8: + case Instruction::SHR_INT_LIT8: + case Instruction::USHR_INT_LIT8: { + // Same as res = op + 2 operands, except use vB as operand 2 + uint16_t operand1 = GetOperandValue(mir->ssa_rep->uses[0]); + uint16_t operand2 = LookupValue(Instruction::CONST, mir->dalvikInsn.vB, 0, 0); + uint16_t res = LookupValue(opcode, operand1, operand2, NO_VALUE); + SetOperandValue(mir->ssa_rep->defs[0], res); + } + break; + + case Instruction::AGET_WIDE: + case Instruction::AGET: + case Instruction::AGET_OBJECT: + case Instruction::AGET_BOOLEAN: + case Instruction::AGET_BYTE: + case Instruction::AGET_CHAR: + case Instruction::AGET_SHORT: { + uint16_t array = GetOperandValue(mir->ssa_rep->uses[0]); + if (null_checked_.find(array) != null_checked_.end()) { + if (cu_->verbose) { + LOG(INFO) << "Removing null check for 0x" << std::hex << mir->offset; + } + mir->optimization_flags |= MIR_IGNORE_NULL_CHECK; + } else { + null_checked_.insert(array); + } + uint16_t index = GetOperandValue(mir->ssa_rep->uses[1]); + if (ValueExists(ARRAY_REF, array, index, NO_VALUE)) { + if (cu_->verbose) { + LOG(INFO) << "Removing range check for 0x" << std::hex << mir->offset; + } + mir->optimization_flags |= MIR_IGNORE_RANGE_CHECK; + } + mir->meta.throw_insn->optimization_flags |= mir->optimization_flags; + // Use side effect to note range check completed. + (void)LookupValue(ARRAY_REF, array, index, NO_VALUE); + // Establish value number for loaded register. Note use of memory version. + uint16_t memory_version = GetMemoryVersion(array, NO_VALUE); + uint16_t res = LookupValue(ARRAY_REF, array, index, memory_version); + if (opcode == Instruction::AGET_WIDE) { + SetOperandValueWide(mir->ssa_rep->defs[0], res); + } else { + SetOperandValue(mir->ssa_rep->defs[0], res); + } + } + break; + + case Instruction::APUT_WIDE: + case Instruction::APUT: + case Instruction::APUT_OBJECT: + case Instruction::APUT_SHORT: + case Instruction::APUT_CHAR: + case Instruction::APUT_BYTE: + case Instruction::APUT_BOOLEAN: { + int array_idx = (opcode == Instruction::APUT_WIDE) ? 2 : 1; + int index_idx = array_idx + 1; + uint16_t array = GetOperandValue(mir->ssa_rep->uses[array_idx]); + if (null_checked_.find(array) != null_checked_.end()) { + if (cu_->verbose) { + LOG(INFO) << "Removing range check for 0x" << std::hex << mir->offset; + } + mir->optimization_flags |= MIR_IGNORE_NULL_CHECK; + } else { + null_checked_.insert(array); + } + uint16_t index = GetOperandValue(mir->ssa_rep->uses[index_idx]); + if (ValueExists(ARRAY_REF, array, index, NO_VALUE)) { + if (cu_->verbose) { + LOG(INFO) << "Removing range check for 0x" << std::hex << mir->offset; + } + mir->optimization_flags |= MIR_IGNORE_RANGE_CHECK; + } + mir->meta.throw_insn->optimization_flags |= mir->optimization_flags; + // Use side effect to note range check completed. + (void)LookupValue(ARRAY_REF, array, index, NO_VALUE); + // Rev the memory version + AdvanceMemoryVersion(array, NO_VALUE); + } + break; + + case Instruction::IGET_OBJECT: + case Instruction::IGET_WIDE: + case Instruction::IGET: + case Instruction::IGET_CHAR: + case Instruction::IGET_SHORT: + case Instruction::IGET_BOOLEAN: + case Instruction::IGET_BYTE: { + uint16_t base = GetOperandValue(mir->ssa_rep->uses[0]); + if (null_checked_.find(base) != null_checked_.end()) { + if (cu_->verbose) { + LOG(INFO) << "Removing null check for 0x" << std::hex << mir->offset; + } + mir->optimization_flags |= MIR_IGNORE_NULL_CHECK; + } else { + null_checked_.insert(base); + } + mir->meta.throw_insn->optimization_flags |= mir->optimization_flags; + uint16_t field_ref = mir->dalvikInsn.vC; + uint16_t memory_version = GetMemoryVersion(base, field_ref); + if (opcode == Instruction::IGET_WIDE) { + uint16_t res = LookupValue(Instruction::IGET_WIDE, base, field_ref, memory_version); + SetOperandValueWide(mir->ssa_rep->defs[0], res); + } else { + uint16_t res = LookupValue(Instruction::IGET, base, field_ref, memory_version); + SetOperandValue(mir->ssa_rep->defs[0], res); + } + } + break; + + case Instruction::IPUT_WIDE: + case Instruction::IPUT_OBJECT: + case Instruction::IPUT: + case Instruction::IPUT_BOOLEAN: + case Instruction::IPUT_BYTE: + case Instruction::IPUT_CHAR: + case Instruction::IPUT_SHORT: { + int base_reg = (opcode == Instruction::IPUT_WIDE) ? 2 : 1; + uint16_t base = GetOperandValue(mir->ssa_rep->uses[base_reg]); + if (null_checked_.find(base) != null_checked_.end()) { + if (cu_->verbose) { + LOG(INFO) << "Removing null check for 0x" << std::hex << mir->offset; + } + mir->optimization_flags |= MIR_IGNORE_NULL_CHECK; + } else { + null_checked_.insert(base); + } + mir->meta.throw_insn->optimization_flags |= mir->optimization_flags; + uint16_t field_ref = mir->dalvikInsn.vC; + AdvanceMemoryVersion(base, field_ref); + } + break; + + case Instruction::SGET_OBJECT: + case Instruction::SGET: + case Instruction::SGET_BOOLEAN: + case Instruction::SGET_BYTE: + case Instruction::SGET_CHAR: + case Instruction::SGET_SHORT: + case Instruction::SGET_WIDE: { + uint16_t field_ref = mir->dalvikInsn.vB; + uint16_t memory_version = GetMemoryVersion(NO_VALUE, field_ref); + if (opcode == Instruction::SGET_WIDE) { + uint16_t res = LookupValue(Instruction::SGET_WIDE, NO_VALUE, field_ref, memory_version); + SetOperandValueWide(mir->ssa_rep->defs[0], res); + } else { + uint16_t res = LookupValue(Instruction::SGET, NO_VALUE, field_ref, memory_version); + SetOperandValue(mir->ssa_rep->defs[0], res); + } + } + break; + + case Instruction::SPUT_OBJECT: + case Instruction::SPUT: + case Instruction::SPUT_BOOLEAN: + case Instruction::SPUT_BYTE: + case Instruction::SPUT_CHAR: + case Instruction::SPUT_SHORT: + case Instruction::SPUT_WIDE: { + uint16_t field_ref = mir->dalvikInsn.vB; + AdvanceMemoryVersion(NO_VALUE, field_ref); + } + break; + + } + return res; +} + +} // namespace art diff --git a/src/compiler/dex/local_value_numbering.h b/src/compiler/dex/local_value_numbering.h new file mode 100644 index 0000000000000000000000000000000000000000..beb4cea733adeedcdcfde598bed366b774c18c3d --- /dev/null +++ b/src/compiler/dex/local_value_numbering.h @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_SRC_COMPILER_DEX_LOCAL_VALUE_NUMBERING_H_ +#define ART_SRC_COMPILER_DEX_LOCAL_VALUE_NUMBERING_H_ + +#include "compiler_internals.h" + +#define NO_VALUE 0xffff +#define ARRAY_REF 0xfffe + +namespace art { + +// Key is s_reg, value is value name. +typedef SafeMap SregValueMap; +// Key is concatenation of quad, value is value name. +typedef SafeMap ValueMap; +// Key represents a memory address, value is generation. +typedef SafeMap MemoryVersionMap; + +class LocalValueNumbering { + public: + LocalValueNumbering(CompilationUnit* cu) : cu_(cu) {}; + + static uint64_t BuildKey(uint16_t op, uint16_t operand1, uint16_t operand2, uint16_t modifier) { + return (static_cast(op) << 48 | static_cast(operand1) << 32 | + static_cast(operand2) << 16 | static_cast(modifier)); + }; + + uint16_t LookupValue(uint16_t op, uint16_t operand1, uint16_t operand2, uint16_t modifier) { + uint16_t res; + uint64_t key = BuildKey(op, operand1, operand2, modifier); + ValueMap::iterator it = value_map_.find(key); + if (it != value_map_.end()) { + res = it->second; + } else { + res = value_map_.size() + 1; + value_map_.Put(key, res); + } + return res; + }; + + bool ValueExists(uint16_t op, uint16_t operand1, uint16_t operand2, uint16_t modifier) const { + uint64_t key = BuildKey(op, operand1, operand2, modifier); + ValueMap::const_iterator it = value_map_.find(key); + return (it != value_map_.end()); + }; + + uint16_t GetMemoryVersion(uint16_t base, uint16_t field) { + uint32_t key = (base << 16) | field; + uint16_t res; + MemoryVersionMap::iterator it = memory_version_map_.find(key); + if (it == memory_version_map_.end()) { + res = 0; + memory_version_map_.Put(key, res); + } else { + res = it->second; + } + return res; + }; + + void AdvanceMemoryVersion(uint16_t base, uint16_t field) { + uint32_t key = (base << 16) | field; + MemoryVersionMap::iterator it = memory_version_map_.find(key); + if (it == memory_version_map_.end()) { + memory_version_map_.Put(key, 0); + } else { + it->second++; + } + }; + + void SetOperandValue(uint16_t s_reg, uint16_t value) { + SregValueMap::iterator it = sreg_value_map_.find(s_reg); + if (it != sreg_value_map_.end()) { + DCHECK_EQ(it->second, value); + } else { + sreg_value_map_.Put(s_reg, value); + } + }; + + uint16_t GetOperandValue(int s_reg) { + uint16_t res = NO_VALUE; + SregValueMap::iterator it = sreg_value_map_.find(s_reg); + if (it != sreg_value_map_.end()) { + res = it->second; + } else { + // First use + res = LookupValue(NO_VALUE, s_reg, NO_VALUE, NO_VALUE); + sreg_value_map_.Put(s_reg, res); + } + return res; + }; + + void SetOperandValueWide(uint16_t s_reg, uint16_t value) { + SregValueMap::iterator it = sreg_wide_value_map_.find(s_reg); + if (it != sreg_wide_value_map_.end()) { + DCHECK_EQ(it->second, value); + } else { + sreg_wide_value_map_.Put(s_reg, value); + } + }; + + uint16_t GetOperandValueWide(int s_reg) { + uint16_t res = NO_VALUE; + SregValueMap::iterator it = sreg_wide_value_map_.find(s_reg); + if (it != sreg_wide_value_map_.end()) { + res = it->second; + } else { + // First use + res = LookupValue(NO_VALUE, s_reg, NO_VALUE, NO_VALUE); + sreg_wide_value_map_.Put(s_reg, res); + } + return res; + }; + + uint16_t GetValueNumber(MIR* mir); + + private: + CompilationUnit* const cu_; + SregValueMap sreg_value_map_; + SregValueMap sreg_wide_value_map_; + ValueMap value_map_; + MemoryVersionMap memory_version_map_; + std::set null_checked_; + +}; + +} // namespace art + +#endif // ART_SRC_COMPILER_DEX_LOCAL_VALUE_NUMBERING_H_ diff --git a/src/compiler/dex/mir_dataflow.cc b/src/compiler/dex/mir_dataflow.cc new file mode 100644 index 0000000000000000000000000000000000000000..9f61d73d6b6a6be1fe82ac7012d986a4a3bfdeba --- /dev/null +++ b/src/compiler/dex/mir_dataflow.cc @@ -0,0 +1,1367 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compiler_internals.h" +#include "local_value_numbering.h" +#include "dataflow_iterator.h" + +namespace art { + +/* + * Main table containing data flow attributes for each bytecode. The + * first kNumPackedOpcodes entries are for Dalvik bytecode + * instructions, where extended opcode at the MIR level are appended + * afterwards. + * + * TODO - many optimization flags are incomplete - they will only limit the + * scope of optimizations but will not cause mis-optimizations. + */ +const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { + // 00 NOP + DF_NOP, + + // 01 MOVE vA, vB + DF_DA | DF_UB | DF_IS_MOVE, + + // 02 MOVE_FROM16 vAA, vBBBB + DF_DA | DF_UB | DF_IS_MOVE, + + // 03 MOVE_16 vAAAA, vBBBB + DF_DA | DF_UB | DF_IS_MOVE, + + // 04 MOVE_WIDE vA, vB + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_IS_MOVE, + + // 05 MOVE_WIDE_FROM16 vAA, vBBBB + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_IS_MOVE, + + // 06 MOVE_WIDE_16 vAAAA, vBBBB + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_IS_MOVE, + + // 07 MOVE_OBJECT vA, vB + DF_DA | DF_UB | DF_NULL_TRANSFER_0 | DF_IS_MOVE | DF_REF_A | DF_REF_B, + + // 08 MOVE_OBJECT_FROM16 vAA, vBBBB + DF_DA | DF_UB | DF_NULL_TRANSFER_0 | DF_IS_MOVE | DF_REF_A | DF_REF_B, + + // 09 MOVE_OBJECT_16 vAAAA, vBBBB + DF_DA | DF_UB | DF_NULL_TRANSFER_0 | DF_IS_MOVE | DF_REF_A | DF_REF_B, + + // 0A MOVE_RESULT vAA + DF_DA, + + // 0B MOVE_RESULT_WIDE vAA + DF_DA | DF_A_WIDE, + + // 0C MOVE_RESULT_OBJECT vAA + DF_DA | DF_REF_A, + + // 0D MOVE_EXCEPTION vAA + DF_DA | DF_REF_A, + + // 0E RETURN_VOID + DF_NOP, + + // 0F RETURN vAA + DF_UA, + + // 10 RETURN_WIDE vAA + DF_UA | DF_A_WIDE, + + // 11 RETURN_OBJECT vAA + DF_UA | DF_REF_A, + + // 12 CONST_4 vA, #+B + DF_DA | DF_SETS_CONST, + + // 13 CONST_16 vAA, #+BBBB + DF_DA | DF_SETS_CONST, + + // 14 CONST vAA, #+BBBBBBBB + DF_DA | DF_SETS_CONST, + + // 15 CONST_HIGH16 VAA, #+BBBB0000 + DF_DA | DF_SETS_CONST, + + // 16 CONST_WIDE_16 vAA, #+BBBB + DF_DA | DF_A_WIDE | DF_SETS_CONST, + + // 17 CONST_WIDE_32 vAA, #+BBBBBBBB + DF_DA | DF_A_WIDE | DF_SETS_CONST, + + // 18 CONST_WIDE vAA, #+BBBBBBBBBBBBBBBB + DF_DA | DF_A_WIDE | DF_SETS_CONST, + + // 19 CONST_WIDE_HIGH16 vAA, #+BBBB000000000000 + DF_DA | DF_A_WIDE | DF_SETS_CONST, + + // 1A CONST_STRING vAA, string@BBBB + DF_DA | DF_REF_A, + + // 1B CONST_STRING_JUMBO vAA, string@BBBBBBBB + DF_DA | DF_REF_A, + + // 1C CONST_CLASS vAA, type@BBBB + DF_DA | DF_REF_A, + + // 1D MONITOR_ENTER vAA + DF_UA | DF_NULL_CHK_0 | DF_REF_A, + + // 1E MONITOR_EXIT vAA + DF_UA | DF_NULL_CHK_0 | DF_REF_A, + + // 1F CHK_CAST vAA, type@BBBB + DF_UA | DF_REF_A | DF_UMS, + + // 20 INSTANCE_OF vA, vB, type@CCCC + DF_DA | DF_UB | DF_CORE_A | DF_REF_B | DF_UMS, + + // 21 ARRAY_LENGTH vA, vB + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_CORE_A | DF_REF_B, + + // 22 NEW_INSTANCE vAA, type@BBBB + DF_DA | DF_NON_NULL_DST | DF_REF_A | DF_UMS, + + // 23 NEW_ARRAY vA, vB, type@CCCC + DF_DA | DF_UB | DF_NON_NULL_DST | DF_REF_A | DF_CORE_B | DF_UMS, + + // 24 FILLED_NEW_ARRAY {vD, vE, vF, vG, vA} + DF_FORMAT_35C | DF_NON_NULL_RET | DF_UMS, + + // 25 FILLED_NEW_ARRAY_RANGE {vCCCC .. vNNNN}, type@BBBB + DF_FORMAT_3RC | DF_NON_NULL_RET | DF_UMS, + + // 26 FILL_ARRAY_DATA vAA, +BBBBBBBB + DF_UA | DF_REF_A | DF_UMS, + + // 27 THROW vAA + DF_UA | DF_REF_A | DF_UMS, + + // 28 GOTO + DF_NOP, + + // 29 GOTO_16 + DF_NOP, + + // 2A GOTO_32 + DF_NOP, + + // 2B PACKED_SWITCH vAA, +BBBBBBBB + DF_UA, + + // 2C SPARSE_SWITCH vAA, +BBBBBBBB + DF_UA, + + // 2D CMPL_FLOAT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_FP_B | DF_FP_C | DF_CORE_A, + + // 2E CMPG_FLOAT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_FP_B | DF_FP_C | DF_CORE_A, + + // 2F CMPL_DOUBLE vAA, vBB, vCC + DF_DA | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_FP_B | DF_FP_C | DF_CORE_A, + + // 30 CMPG_DOUBLE vAA, vBB, vCC + DF_DA | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_FP_B | DF_FP_C | DF_CORE_A, + + // 31 CMP_LONG vAA, vBB, vCC + DF_DA | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 32 IF_EQ vA, vB, +CCCC + DF_UA | DF_UB, + + // 33 IF_NE vA, vB, +CCCC + DF_UA | DF_UB, + + // 34 IF_LT vA, vB, +CCCC + DF_UA | DF_UB, + + // 35 IF_GE vA, vB, +CCCC + DF_UA | DF_UB, + + // 36 IF_GT vA, vB, +CCCC + DF_UA | DF_UB, + + // 37 IF_LE vA, vB, +CCCC + DF_UA | DF_UB, + + // 38 IF_EQZ vAA, +BBBB + DF_UA, + + // 39 IF_NEZ vAA, +BBBB + DF_UA, + + // 3A IF_LTZ vAA, +BBBB + DF_UA, + + // 3B IF_GEZ vAA, +BBBB + DF_UA, + + // 3C IF_GTZ vAA, +BBBB + DF_UA, + + // 3D IF_LEZ vAA, +BBBB + DF_UA, + + // 3E UNUSED_3E + DF_NOP, + + // 3F UNUSED_3F + DF_NOP, + + // 40 UNUSED_40 + DF_NOP, + + // 41 UNUSED_41 + DF_NOP, + + // 42 UNUSED_42 + DF_NOP, + + // 43 UNUSED_43 + DF_NOP, + + // 44 AGET vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_NULL_CHK_0 | DF_RANGE_CHK_1 | DF_REF_B | DF_CORE_C, + + // 45 AGET_WIDE vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_UC | DF_NULL_CHK_0 | DF_RANGE_CHK_1 | DF_REF_B | DF_CORE_C, + + // 46 AGET_OBJECT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_NULL_CHK_0 | DF_RANGE_CHK_1 | DF_REF_A | DF_REF_B | DF_CORE_C, + + // 47 AGET_BOOLEAN vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_NULL_CHK_0 | DF_RANGE_CHK_1 | DF_REF_B | DF_CORE_C, + + // 48 AGET_BYTE vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_NULL_CHK_0 | DF_RANGE_CHK_1 | DF_REF_B | DF_CORE_C, + + // 49 AGET_CHAR vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_NULL_CHK_0 | DF_RANGE_CHK_1 | DF_REF_B | DF_CORE_C, + + // 4A AGET_SHORT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_NULL_CHK_0 | DF_RANGE_CHK_1 | DF_REF_B | DF_CORE_C, + + // 4B APUT vAA, vBB, vCC + DF_UA | DF_UB | DF_UC | DF_NULL_CHK_1 | DF_RANGE_CHK_2 | DF_REF_B | DF_CORE_C, + + // 4C APUT_WIDE vAA, vBB, vCC + DF_UA | DF_A_WIDE | DF_UB | DF_UC | DF_NULL_CHK_2 | DF_RANGE_CHK_3 | DF_REF_B | DF_CORE_C, + + // 4D APUT_OBJECT vAA, vBB, vCC + DF_UA | DF_UB | DF_UC | DF_NULL_CHK_1 | DF_RANGE_CHK_2 | DF_REF_A | DF_REF_B | DF_CORE_C, + + // 4E APUT_BOOLEAN vAA, vBB, vCC + DF_UA | DF_UB | DF_UC | DF_NULL_CHK_1 | DF_RANGE_CHK_2 | DF_REF_B | DF_CORE_C, + + // 4F APUT_BYTE vAA, vBB, vCC + DF_UA | DF_UB | DF_UC | DF_NULL_CHK_1 | DF_RANGE_CHK_2 | DF_REF_B | DF_CORE_C, + + // 50 APUT_CHAR vAA, vBB, vCC + DF_UA | DF_UB | DF_UC | DF_NULL_CHK_1 | DF_RANGE_CHK_2 | DF_REF_B | DF_CORE_C, + + // 51 APUT_SHORT vAA, vBB, vCC + DF_UA | DF_UB | DF_UC | DF_NULL_CHK_1 | DF_RANGE_CHK_2 | DF_REF_B | DF_CORE_C, + + // 52 IGET vA, vB, field@CCCC + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + + // 53 IGET_WIDE vA, vB, field@CCCC + DF_DA | DF_A_WIDE | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + + // 54 IGET_OBJECT vA, vB, field@CCCC + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_A | DF_REF_B, + + // 55 IGET_BOOLEAN vA, vB, field@CCCC + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + + // 56 IGET_BYTE vA, vB, field@CCCC + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + + // 57 IGET_CHAR vA, vB, field@CCCC + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + + // 58 IGET_SHORT vA, vB, field@CCCC + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + + // 59 IPUT vA, vB, field@CCCC + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B, + + // 5A IPUT_WIDE vA, vB, field@CCCC + DF_UA | DF_A_WIDE | DF_UB | DF_NULL_CHK_2 | DF_REF_B, + + // 5B IPUT_OBJECT vA, vB, field@CCCC + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_A | DF_REF_B, + + // 5C IPUT_BOOLEAN vA, vB, field@CCCC + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B, + + // 5D IPUT_BYTE vA, vB, field@CCCC + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B, + + // 5E IPUT_CHAR vA, vB, field@CCCC + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B, + + // 5F IPUT_SHORT vA, vB, field@CCCC + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B, + + // 60 SGET vAA, field@BBBB + DF_DA | DF_UMS, + + // 61 SGET_WIDE vAA, field@BBBB + DF_DA | DF_A_WIDE | DF_UMS, + + // 62 SGET_OBJECT vAA, field@BBBB + DF_DA | DF_REF_A | DF_UMS, + + // 63 SGET_BOOLEAN vAA, field@BBBB + DF_DA | DF_UMS, + + // 64 SGET_BYTE vAA, field@BBBB + DF_DA | DF_UMS, + + // 65 SGET_CHAR vAA, field@BBBB + DF_DA | DF_UMS, + + // 66 SGET_SHORT vAA, field@BBBB + DF_DA | DF_UMS, + + // 67 SPUT vAA, field@BBBB + DF_UA | DF_UMS, + + // 68 SPUT_WIDE vAA, field@BBBB + DF_UA | DF_A_WIDE | DF_UMS, + + // 69 SPUT_OBJECT vAA, field@BBBB + DF_UA | DF_REF_A | DF_UMS, + + // 6A SPUT_BOOLEAN vAA, field@BBBB + DF_UA | DF_UMS, + + // 6B SPUT_BYTE vAA, field@BBBB + DF_UA | DF_UMS, + + // 6C SPUT_CHAR vAA, field@BBBB + DF_UA | DF_UMS, + + // 6D SPUT_SHORT vAA, field@BBBB + DF_UA | DF_UMS, + + // 6E INVOKE_VIRTUAL {vD, vE, vF, vG, vA} + DF_FORMAT_35C | DF_NULL_CHK_OUT0 | DF_UMS, + + // 6F INVOKE_SUPER {vD, vE, vF, vG, vA} + DF_FORMAT_35C | DF_NULL_CHK_OUT0 | DF_UMS, + + // 70 INVOKE_DIRECT {vD, vE, vF, vG, vA} + DF_FORMAT_35C | DF_NULL_CHK_OUT0 | DF_UMS, + + // 71 INVOKE_STATIC {vD, vE, vF, vG, vA} + DF_FORMAT_35C | DF_UMS, + + // 72 INVOKE_INTERFACE {vD, vE, vF, vG, vA} + DF_FORMAT_35C | DF_UMS, + + // 73 UNUSED_73 + DF_NOP, + + // 74 INVOKE_VIRTUAL_RANGE {vCCCC .. vNNNN} + DF_FORMAT_3RC | DF_NULL_CHK_OUT0 | DF_UMS, + + // 75 INVOKE_SUPER_RANGE {vCCCC .. vNNNN} + DF_FORMAT_3RC | DF_NULL_CHK_OUT0 | DF_UMS, + + // 76 INVOKE_DIRECT_RANGE {vCCCC .. vNNNN} + DF_FORMAT_3RC | DF_NULL_CHK_OUT0 | DF_UMS, + + // 77 INVOKE_STATIC_RANGE {vCCCC .. vNNNN} + DF_FORMAT_3RC | DF_UMS, + + // 78 INVOKE_INTERFACE_RANGE {vCCCC .. vNNNN} + DF_FORMAT_3RC | DF_UMS, + + // 79 UNUSED_79 + DF_NOP, + + // 7A UNUSED_7A + DF_NOP, + + // 7B NEG_INT vA, vB + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // 7C NOT_INT vA, vB + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // 7D NEG_LONG vA, vB + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B, + + // 7E NOT_LONG vA, vB + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B, + + // 7F NEG_FLOAT vA, vB + DF_DA | DF_UB | DF_FP_A | DF_FP_B, + + // 80 NEG_DOUBLE vA, vB + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_FP_A | DF_FP_B, + + // 81 INT_TO_LONG vA, vB + DF_DA | DF_A_WIDE | DF_UB | DF_CORE_A | DF_CORE_B, + + // 82 INT_TO_FLOAT vA, vB + DF_DA | DF_UB | DF_FP_A | DF_CORE_B, + + // 83 INT_TO_DOUBLE vA, vB + DF_DA | DF_A_WIDE | DF_UB | DF_FP_A | DF_CORE_B, + + // 84 LONG_TO_INT vA, vB + DF_DA | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B, + + // 85 LONG_TO_FLOAT vA, vB + DF_DA | DF_UB | DF_B_WIDE | DF_FP_A | DF_CORE_B, + + // 86 LONG_TO_DOUBLE vA, vB + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_FP_A | DF_CORE_B, + + // 87 FLOAT_TO_INT vA, vB + DF_DA | DF_UB | DF_FP_B | DF_CORE_A, + + // 88 FLOAT_TO_LONG vA, vB + DF_DA | DF_A_WIDE | DF_UB | DF_FP_B | DF_CORE_A, + + // 89 FLOAT_TO_DOUBLE vA, vB + DF_DA | DF_A_WIDE | DF_UB | DF_FP_A | DF_FP_B, + + // 8A DOUBLE_TO_INT vA, vB + DF_DA | DF_UB | DF_B_WIDE | DF_FP_B | DF_CORE_A, + + // 8B DOUBLE_TO_LONG vA, vB + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_FP_B | DF_CORE_A, + + // 8C DOUBLE_TO_FLOAT vA, vB + DF_DA | DF_UB | DF_B_WIDE | DF_FP_A | DF_FP_B, + + // 8D INT_TO_BYTE vA, vB + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // 8E INT_TO_CHAR vA, vB + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // 8F INT_TO_SHORT vA, vB + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // 90 ADD_INT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 91 SUB_INT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 92 MUL_INT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 93 DIV_INT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 94 REM_INT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 95 AND_INT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 96 OR_INT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 97 XOR_INT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 98 SHL_INT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 99 SHR_INT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 9A USHR_INT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 9B ADD_LONG vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 9C SUB_LONG vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 9D MUL_LONG vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 9E DIV_LONG vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // 9F REM_LONG vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // A0 AND_LONG vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // A1 OR_LONG vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // A2 XOR_LONG vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // A3 SHL_LONG vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // A4 SHR_LONG vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // A5 USHR_LONG vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + + // A6 ADD_FLOAT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_FP_A | DF_FP_B | DF_FP_C, + + // A7 SUB_FLOAT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_FP_A | DF_FP_B | DF_FP_C, + + // A8 MUL_FLOAT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_FP_A | DF_FP_B | DF_FP_C, + + // A9 DIV_FLOAT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_FP_A | DF_FP_B | DF_FP_C, + + // AA REM_FLOAT vAA, vBB, vCC + DF_DA | DF_UB | DF_UC | DF_FP_A | DF_FP_B | DF_FP_C, + + // AB ADD_DOUBLE vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_FP_A | DF_FP_B | DF_FP_C, + + // AC SUB_DOUBLE vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_FP_A | DF_FP_B | DF_FP_C, + + // AD MUL_DOUBLE vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_FP_A | DF_FP_B | DF_FP_C, + + // AE DIV_DOUBLE vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_FP_A | DF_FP_B | DF_FP_C, + + // AF REM_DOUBLE vAA, vBB, vCC + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_FP_A | DF_FP_B | DF_FP_C, + + // B0 ADD_INT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + + // B1 SUB_INT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + + // B2 MUL_INT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + + // B3 DIV_INT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + + // B4 REM_INT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + + // B5 AND_INT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + + // B6 OR_INT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + + // B7 XOR_INT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + + // B8 SHL_INT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + + // B9 SHR_INT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + + // BA USHR_INT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + + // BB ADD_LONG_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B, + + // BC SUB_LONG_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B, + + // BD MUL_LONG_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B, + + // BE DIV_LONG_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B, + + // BF REM_LONG_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B, + + // C0 AND_LONG_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B, + + // C1 OR_LONG_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B, + + // C2 XOR_LONG_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B, + + // C3 SHL_LONG_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + + // C4 SHR_LONG_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + + // C5 USHR_LONG_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + + // C6 ADD_FLOAT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_FP_A | DF_FP_B, + + // C7 SUB_FLOAT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_FP_A | DF_FP_B, + + // C8 MUL_FLOAT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_FP_A | DF_FP_B, + + // C9 DIV_FLOAT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_FP_A | DF_FP_B, + + // CA REM_FLOAT_2ADDR vA, vB + DF_DA | DF_UA | DF_UB | DF_FP_A | DF_FP_B, + + // CB ADD_DOUBLE_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_FP_A | DF_FP_B, + + // CC SUB_DOUBLE_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_FP_A | DF_FP_B, + + // CD MUL_DOUBLE_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_FP_A | DF_FP_B, + + // CE DIV_DOUBLE_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_FP_A | DF_FP_B, + + // CF REM_DOUBLE_2ADDR vA, vB + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_FP_A | DF_FP_B, + + // D0 ADD_INT_LIT16 vA, vB, #+CCCC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // D1 RSUB_INT vA, vB, #+CCCC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // D2 MUL_INT_LIT16 vA, vB, #+CCCC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // D3 DIV_INT_LIT16 vA, vB, #+CCCC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // D4 REM_INT_LIT16 vA, vB, #+CCCC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // D5 AND_INT_LIT16 vA, vB, #+CCCC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // D6 OR_INT_LIT16 vA, vB, #+CCCC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // D7 XOR_INT_LIT16 vA, vB, #+CCCC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // D8 ADD_INT_LIT8 vAA, vBB, #+CC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // D9 RSUB_INT_LIT8 vAA, vBB, #+CC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // DA MUL_INT_LIT8 vAA, vBB, #+CC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // DB DIV_INT_LIT8 vAA, vBB, #+CC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // DC REM_INT_LIT8 vAA, vBB, #+CC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // DD AND_INT_LIT8 vAA, vBB, #+CC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // DE OR_INT_LIT8 vAA, vBB, #+CC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // DF XOR_INT_LIT8 vAA, vBB, #+CC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // E0 SHL_INT_LIT8 vAA, vBB, #+CC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // E1 SHR_INT_LIT8 vAA, vBB, #+CC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // E2 USHR_INT_LIT8 vAA, vBB, #+CC + DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, + + // E3 IGET_VOLATILE + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + + // E4 IPUT_VOLATILE + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B, + + // E5 SGET_VOLATILE + DF_DA | DF_UMS, + + // E6 SPUT_VOLATILE + DF_UA | DF_UMS, + + // E7 IGET_OBJECT_VOLATILE + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_A | DF_REF_B, + + // E8 IGET_WIDE_VOLATILE + DF_DA | DF_A_WIDE | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + + // E9 IPUT_WIDE_VOLATILE + DF_UA | DF_A_WIDE | DF_UB | DF_NULL_CHK_2 | DF_REF_B, + + // EA SGET_WIDE_VOLATILE + DF_DA | DF_A_WIDE | DF_UMS, + + // EB SPUT_WIDE_VOLATILE + DF_UA | DF_A_WIDE | DF_UMS, + + // EC BREAKPOINT + DF_NOP, + + // ED THROW_VERIFICATION_ERROR + DF_NOP | DF_UMS, + + // EE EXECUTE_INLINE + DF_FORMAT_35C, + + // EF EXECUTE_INLINE_RANGE + DF_FORMAT_3RC, + + // F0 INVOKE_OBJECT_INIT_RANGE + DF_NOP | DF_NULL_CHK_0, + + // F1 RETURN_VOID_BARRIER + DF_NOP, + + // F2 IGET_QUICK + DF_DA | DF_UB | DF_NULL_CHK_0, + + // F3 IGET_WIDE_QUICK + DF_DA | DF_A_WIDE | DF_UB | DF_NULL_CHK_0, + + // F4 IGET_OBJECT_QUICK + DF_DA | DF_UB | DF_NULL_CHK_0, + + // F5 IPUT_QUICK + DF_UA | DF_UB | DF_NULL_CHK_1, + + // F6 IPUT_WIDE_QUICK + DF_UA | DF_A_WIDE | DF_UB | DF_NULL_CHK_2, + + // F7 IPUT_OBJECT_QUICK + DF_UA | DF_UB | DF_NULL_CHK_1, + + // F8 INVOKE_VIRTUAL_QUICK + DF_FORMAT_35C | DF_NULL_CHK_OUT0 | DF_UMS, + + // F9 INVOKE_VIRTUAL_QUICK_RANGE + DF_FORMAT_3RC | DF_NULL_CHK_OUT0 | DF_UMS, + + // FA INVOKE_SUPER_QUICK + DF_FORMAT_35C | DF_NULL_CHK_OUT0 | DF_UMS, + + // FB INVOKE_SUPER_QUICK_RANGE + DF_FORMAT_3RC | DF_NULL_CHK_OUT0 | DF_UMS, + + // FC IPUT_OBJECT_VOLATILE + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_A | DF_REF_B, + + // FD SGET_OBJECT_VOLATILE + DF_DA | DF_REF_A | DF_UMS, + + // FE SPUT_OBJECT_VOLATILE + DF_UA | DF_REF_A | DF_UMS, + + // FF UNUSED_FF + DF_NOP, + + // Beginning of extended MIR opcodes + // 100 MIR_PHI + DF_DA | DF_NULL_TRANSFER_N, + + // 101 MIR_COPY + DF_DA | DF_UB | DF_IS_MOVE, + + // 102 MIR_FUSED_CMPL_FLOAT + DF_UA | DF_UB | DF_FP_A | DF_FP_B, + + // 103 MIR_FUSED_CMPG_FLOAT + DF_UA | DF_UB | DF_FP_A | DF_FP_B, + + // 104 MIR_FUSED_CMPL_DOUBLE + DF_UA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_FP_A | DF_FP_B, + + // 105 MIR_FUSED_CMPG_DOUBLE + DF_UA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_FP_A | DF_FP_B, + + // 106 MIR_FUSED_CMP_LONG + DF_UA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B, + + // 107 MIR_NOP + DF_NOP, + + // 108 MIR_NULL_CHECK + 0, + + // 109 MIR_RANGE_CHECK + 0, + + // 110 MIR_DIV_ZERO_CHECK + 0, + + // 111 MIR_CHECK + 0, + + // 112 MIR_CHECKPART2 + 0, + + // 113 MIR_SELECT + DF_DA | DF_UB, +}; + +/* Return the base virtual register for a SSA name */ +int MIRGraph::SRegToVReg(int ssa_reg) const { + return ssa_base_vregs_->Get(ssa_reg); +} + +/* Any register that is used before being defined is considered live-in */ +void MIRGraph::HandleLiveInUse(ArenaBitVector* use_v, ArenaBitVector* def_v, + ArenaBitVector* live_in_v, int dalvik_reg_id) +{ + use_v->SetBit(dalvik_reg_id); + if (!def_v->IsBitSet(dalvik_reg_id)) { + live_in_v->SetBit(dalvik_reg_id); + } +} + +/* Mark a reg as being defined */ +void MIRGraph::HandleDef(ArenaBitVector* def_v, int dalvik_reg_id) +{ + def_v->SetBit(dalvik_reg_id); +} + +/* + * Find out live-in variables for natural loops. Variables that are live-in in + * the main loop body are considered to be defined in the entry block. + */ +bool MIRGraph::FindLocalLiveIn(BasicBlock* bb) +{ + MIR* mir; + ArenaBitVector *use_v, *def_v, *live_in_v; + + if (bb->data_flow_info == NULL) return false; + + use_v = bb->data_flow_info->use_v = + new (arena_) ArenaBitVector(arena_, cu_->num_dalvik_registers, false, kBitMapUse); + def_v = bb->data_flow_info->def_v = + new (arena_) ArenaBitVector(arena_, cu_->num_dalvik_registers, false, kBitMapDef); + live_in_v = bb->data_flow_info->live_in_v = + new (arena_) ArenaBitVector(arena_, cu_->num_dalvik_registers, false, kBitMapLiveIn); + + for (mir = bb->first_mir_insn; mir != NULL; mir = mir->next) { + int df_attributes = oat_data_flow_attributes_[mir->dalvikInsn.opcode]; + DecodedInstruction *d_insn = &mir->dalvikInsn; + + if (df_attributes & DF_HAS_USES) { + if (df_attributes & DF_UA) { + HandleLiveInUse(use_v, def_v, live_in_v, d_insn->vA); + if (df_attributes & DF_A_WIDE) { + HandleLiveInUse(use_v, def_v, live_in_v, d_insn->vA+1); + } + } + if (df_attributes & DF_UB) { + HandleLiveInUse(use_v, def_v, live_in_v, d_insn->vB); + if (df_attributes & DF_B_WIDE) { + HandleLiveInUse(use_v, def_v, live_in_v, d_insn->vB+1); + } + } + if (df_attributes & DF_UC) { + HandleLiveInUse(use_v, def_v, live_in_v, d_insn->vC); + if (df_attributes & DF_C_WIDE) { + HandleLiveInUse(use_v, def_v, live_in_v, d_insn->vC+1); + } + } + } + if (df_attributes & DF_FORMAT_35C) { + for (unsigned int i = 0; i < d_insn->vA; i++) { + HandleLiveInUse(use_v, def_v, live_in_v, d_insn->arg[i]); + } + } + if (df_attributes & DF_FORMAT_3RC) { + for (unsigned int i = 0; i < d_insn->vA; i++) { + HandleLiveInUse(use_v, def_v, live_in_v, d_insn->vC+i); + } + } + if (df_attributes & DF_HAS_DEFS) { + HandleDef(def_v, d_insn->vA); + if (df_attributes & DF_A_WIDE) { + HandleDef(def_v, d_insn->vA+1); + } + } + } + return true; +} + +int MIRGraph::AddNewSReg(int v_reg) +{ + // Compiler temps always have a subscript of 0 + int subscript = (v_reg < 0) ? 0 : ++ssa_last_defs_[v_reg]; + int ssa_reg = GetNumSSARegs(); + SetNumSSARegs(ssa_reg + 1); + ssa_base_vregs_->Insert(v_reg); + ssa_subscripts_->Insert(subscript); + std::string ssa_name = GetSSAName(ssa_reg); + char* name = static_cast(arena_->NewMem(ssa_name.length() + 1, false, + ArenaAllocator::kAllocDFInfo)); + strncpy(name, ssa_name.c_str(), ssa_name.length() + 1); + ssa_strings_->Insert(name); + DCHECK_EQ(ssa_base_vregs_->Size(), ssa_subscripts_->Size()); + return ssa_reg; +} + +/* Find out the latest SSA register for a given Dalvik register */ +void MIRGraph::HandleSSAUse(int* uses, int dalvik_reg, int reg_index) +{ + DCHECK((dalvik_reg >= 0) && (dalvik_reg < cu_->num_dalvik_registers)); + uses[reg_index] = vreg_to_ssa_map_[dalvik_reg]; +} + +/* Setup a new SSA register for a given Dalvik register */ +void MIRGraph::HandleSSADef(int* defs, int dalvik_reg, int reg_index) +{ + DCHECK((dalvik_reg >= 0) && (dalvik_reg < cu_->num_dalvik_registers)); + int ssa_reg = AddNewSReg(dalvik_reg); + vreg_to_ssa_map_[dalvik_reg] = ssa_reg; + defs[reg_index] = ssa_reg; +} + +/* Look up new SSA names for format_35c instructions */ +void MIRGraph::DataFlowSSAFormat35C(MIR* mir) +{ + DecodedInstruction *d_insn = &mir->dalvikInsn; + int num_uses = d_insn->vA; + int i; + + mir->ssa_rep->num_uses = num_uses; + mir->ssa_rep->uses = static_cast(arena_->NewMem(sizeof(int) * num_uses, true, + ArenaAllocator::kAllocDFInfo)); + // NOTE: will be filled in during type & size inference pass + mir->ssa_rep->fp_use = static_cast(arena_->NewMem(sizeof(bool) * num_uses, true, + ArenaAllocator::kAllocDFInfo)); + + for (i = 0; i < num_uses; i++) { + HandleSSAUse(mir->ssa_rep->uses, d_insn->arg[i], i); + } +} + +/* Look up new SSA names for format_3rc instructions */ +void MIRGraph::DataFlowSSAFormat3RC(MIR* mir) +{ + DecodedInstruction *d_insn = &mir->dalvikInsn; + int num_uses = d_insn->vA; + int i; + + mir->ssa_rep->num_uses = num_uses; + mir->ssa_rep->uses = static_cast(arena_->NewMem(sizeof(int) * num_uses, true, + ArenaAllocator::kAllocDFInfo)); + // NOTE: will be filled in during type & size inference pass + mir->ssa_rep->fp_use = static_cast(arena_->NewMem(sizeof(bool) * num_uses, true, + ArenaAllocator::kAllocDFInfo)); + + for (i = 0; i < num_uses; i++) { + HandleSSAUse(mir->ssa_rep->uses, d_insn->vC+i, i); + } +} + +/* Entry function to convert a block into SSA representation */ +bool MIRGraph::DoSSAConversion(BasicBlock* bb) +{ + MIR* mir; + + if (bb->data_flow_info == NULL) return false; + + for (mir = bb->first_mir_insn; mir != NULL; mir = mir->next) { + mir->ssa_rep = + static_cast(arena_->NewMem(sizeof(SSARepresentation), true, + ArenaAllocator::kAllocDFInfo)); + + int df_attributes = oat_data_flow_attributes_[mir->dalvikInsn.opcode]; + + // If not a pseudo-op, note non-leaf or can throw + if (static_cast(mir->dalvikInsn.opcode) < + static_cast(kNumPackedOpcodes)) { + int flags = Instruction::FlagsOf(mir->dalvikInsn.opcode); + + if (flags & Instruction::kInvoke) { + attributes_ &= ~METHOD_IS_LEAF; + } + } + + int num_uses = 0; + + if (df_attributes & DF_FORMAT_35C) { + DataFlowSSAFormat35C(mir); + continue; + } + + if (df_attributes & DF_FORMAT_3RC) { + DataFlowSSAFormat3RC(mir); + continue; + } + + if (df_attributes & DF_HAS_USES) { + if (df_attributes & DF_UA) { + num_uses++; + if (df_attributes & DF_A_WIDE) { + num_uses ++; + } + } + if (df_attributes & DF_UB) { + num_uses++; + if (df_attributes & DF_B_WIDE) { + num_uses ++; + } + } + if (df_attributes & DF_UC) { + num_uses++; + if (df_attributes & DF_C_WIDE) { + num_uses ++; + } + } + } + + if (num_uses) { + mir->ssa_rep->num_uses = num_uses; + mir->ssa_rep->uses = static_cast(arena_->NewMem(sizeof(int) * num_uses, false, + ArenaAllocator::kAllocDFInfo)); + mir->ssa_rep->fp_use = static_cast(arena_->NewMem(sizeof(bool) * num_uses, false, + ArenaAllocator::kAllocDFInfo)); + } + + int num_defs = 0; + + if (df_attributes & DF_HAS_DEFS) { + num_defs++; + if (df_attributes & DF_A_WIDE) { + num_defs++; + } + } + + if (num_defs) { + mir->ssa_rep->num_defs = num_defs; + mir->ssa_rep->defs = static_cast(arena_->NewMem(sizeof(int) * num_defs, false, + ArenaAllocator::kAllocDFInfo)); + mir->ssa_rep->fp_def = static_cast(arena_->NewMem(sizeof(bool) * num_defs, false, + ArenaAllocator::kAllocDFInfo)); + } + + DecodedInstruction *d_insn = &mir->dalvikInsn; + + if (df_attributes & DF_HAS_USES) { + num_uses = 0; + if (df_attributes & DF_UA) { + mir->ssa_rep->fp_use[num_uses] = df_attributes & DF_FP_A; + HandleSSAUse(mir->ssa_rep->uses, d_insn->vA, num_uses++); + if (df_attributes & DF_A_WIDE) { + mir->ssa_rep->fp_use[num_uses] = df_attributes & DF_FP_A; + HandleSSAUse(mir->ssa_rep->uses, d_insn->vA+1, num_uses++); + } + } + if (df_attributes & DF_UB) { + mir->ssa_rep->fp_use[num_uses] = df_attributes & DF_FP_B; + HandleSSAUse(mir->ssa_rep->uses, d_insn->vB, num_uses++); + if (df_attributes & DF_B_WIDE) { + mir->ssa_rep->fp_use[num_uses] = df_attributes & DF_FP_B; + HandleSSAUse(mir->ssa_rep->uses, d_insn->vB+1, num_uses++); + } + } + if (df_attributes & DF_UC) { + mir->ssa_rep->fp_use[num_uses] = df_attributes & DF_FP_C; + HandleSSAUse(mir->ssa_rep->uses, d_insn->vC, num_uses++); + if (df_attributes & DF_C_WIDE) { + mir->ssa_rep->fp_use[num_uses] = df_attributes & DF_FP_C; + HandleSSAUse(mir->ssa_rep->uses, d_insn->vC+1, num_uses++); + } + } + } + if (df_attributes & DF_HAS_DEFS) { + mir->ssa_rep->fp_def[0] = df_attributes & DF_FP_A; + HandleSSADef(mir->ssa_rep->defs, d_insn->vA, 0); + if (df_attributes & DF_A_WIDE) { + mir->ssa_rep->fp_def[1] = df_attributes & DF_FP_A; + HandleSSADef(mir->ssa_rep->defs, d_insn->vA+1, 1); + } + } + } + + /* + * Take a snapshot of Dalvik->SSA mapping at the end of each block. The + * input to PHI nodes can be derived from the snapshot of all + * predecessor blocks. + */ + bb->data_flow_info->vreg_to_ssa_map = + static_cast(arena_->NewMem(sizeof(int) * cu_->num_dalvik_registers, false, + ArenaAllocator::kAllocDFInfo)); + + memcpy(bb->data_flow_info->vreg_to_ssa_map, vreg_to_ssa_map_, + sizeof(int) * cu_->num_dalvik_registers); + return true; +} + +/* Setup the basic data structures for SSA conversion */ +void MIRGraph::CompilerInitializeSSAConversion() +{ + size_t num_dalvik_reg = cu_->num_dalvik_registers; + + ssa_base_vregs_ = new (arena_) GrowableArray(arena_, num_dalvik_reg + GetDefCount() + 128, + kGrowableArraySSAtoDalvikMap); + ssa_subscripts_ = new (arena_) GrowableArray(arena_, num_dalvik_reg + GetDefCount() + 128, + kGrowableArraySSAtoDalvikMap); + ssa_strings_ = new (arena_) GrowableArray(arena_, num_dalvik_reg + GetDefCount() + 128, + kGrowableArraySSAtoDalvikMap); + /* + * Initial number of SSA registers is equal to the number of Dalvik + * registers. + */ + SetNumSSARegs(num_dalvik_reg); + + /* + * Initialize the SSA2Dalvik map list. For the first num_dalvik_reg elements, + * the subscript is 0 so we use the ENCODE_REG_SUB macro to encode the value + * into "(0 << 16) | i" + */ + for (unsigned int i = 0; i < num_dalvik_reg; i++) { + ssa_base_vregs_->Insert(i); + ssa_subscripts_->Insert(0); + std::string ssa_name = GetSSAName(i); + char* name = static_cast(arena_->NewMem(ssa_name.length() + 1, true, + ArenaAllocator::kAllocDFInfo)); + strncpy(name, ssa_name.c_str(), ssa_name.length() + 1); + ssa_strings_->Insert(name); + } + + /* + * Initialize the DalvikToSSAMap map. There is one entry for each + * Dalvik register, and the SSA names for those are the same. + */ + vreg_to_ssa_map_ = + static_cast(arena_->NewMem(sizeof(int) * num_dalvik_reg, false, + ArenaAllocator::kAllocDFInfo)); + /* Keep track of the higest def for each dalvik reg */ + ssa_last_defs_ = + static_cast(arena_->NewMem(sizeof(int) * num_dalvik_reg, false, + ArenaAllocator::kAllocDFInfo)); + + for (unsigned int i = 0; i < num_dalvik_reg; i++) { + vreg_to_ssa_map_[i] = i; + ssa_last_defs_[i] = 0; + } + + /* Add ssa reg for Method* */ + method_sreg_ = AddNewSReg(SSA_METHOD_BASEREG); + + /* + * Allocate the BasicBlockDataFlow structure for the entry and code blocks + */ + GrowableArray::Iterator iterator(&block_list_); + + while (true) { + BasicBlock* bb = iterator.Next(); + if (bb == NULL) break; + if (bb->hidden == true) continue; + if (bb->block_type == kDalvikByteCode || + bb->block_type == kEntryBlock || + bb->block_type == kExitBlock) { + bb->data_flow_info = + static_cast(arena_->NewMem(sizeof(BasicBlockDataFlow), true, + ArenaAllocator::kAllocDFInfo)); + } + } +} + +/* + * This function will make a best guess at whether the invoke will + * end up using Method*. It isn't critical to get it exactly right, + * and attempting to do would involve more complexity than it's + * worth. + */ +bool MIRGraph::InvokeUsesMethodStar(MIR* mir) +{ + InvokeType type; + Instruction::Code opcode = mir->dalvikInsn.opcode; + switch (opcode) { + case Instruction::INVOKE_STATIC: + case Instruction::INVOKE_STATIC_RANGE: + type = kStatic; + break; + case Instruction::INVOKE_DIRECT: + case Instruction::INVOKE_DIRECT_RANGE: + type = kDirect; + break; + case Instruction::INVOKE_VIRTUAL: + case Instruction::INVOKE_VIRTUAL_RANGE: + type = kVirtual; + break; + case Instruction::INVOKE_INTERFACE: + case Instruction::INVOKE_INTERFACE_RANGE: + return false; + case Instruction::INVOKE_SUPER_RANGE: + case Instruction::INVOKE_SUPER: + type = kSuper; + break; + default: + LOG(WARNING) << "Unexpected invoke op: " << opcode; + return false; + } + DexCompilationUnit m_unit(cu_); + // TODO: add a flag so we don't counts the stats for this twice + uint32_t dex_method_idx = mir->dalvikInsn.vB; + int vtable_idx; + uintptr_t direct_code; + uintptr_t direct_method; + uint32_t current_offset = static_cast(current_offset_); + bool fast_path = + cu_->compiler_driver->ComputeInvokeInfo(dex_method_idx, current_offset, + &m_unit, type, + vtable_idx, direct_code, + direct_method) && + !(cu_->enable_debug & (1 << kDebugSlowInvokePath)); + return (((type == kDirect) || (type == kStatic)) && + fast_path && ((direct_code == 0) || (direct_method == 0))); +} + +/* + * Count uses, weighting by loop nesting depth. This code only + * counts explicitly used s_regs. A later phase will add implicit + * counts for things such as Method*, null-checked references, etc. + */ +bool MIRGraph::CountUses(struct BasicBlock* bb) +{ + if (bb->block_type != kDalvikByteCode) { + return false; + } + for (MIR* mir = bb->first_mir_insn; (mir != NULL); mir = mir->next) { + if (mir->ssa_rep == NULL) { + continue; + } + uint32_t weight = std::min(16U, static_cast(bb->nesting_depth)); + for (int i = 0; i < mir->ssa_rep->num_uses; i++) { + int s_reg = mir->ssa_rep->uses[i]; + raw_use_counts_.Increment(s_reg); + use_counts_.Put(s_reg, use_counts_.Get(s_reg) + (1 << weight)); + } + if (!(cu_->disable_opt & (1 << kPromoteCompilerTemps))) { + int df_attributes = oat_data_flow_attributes_[mir->dalvikInsn.opcode]; + // Implicit use of Method* ? */ + if (df_attributes & DF_UMS) { + /* + * Some invokes will not use Method* - need to perform test similar + * to that found in GenInvoke() to decide whether to count refs + * for Method* on invoke-class opcodes. + * TODO: refactor for common test here, save results for GenInvoke + */ + int uses_method_star = true; + if ((df_attributes & (DF_FORMAT_35C | DF_FORMAT_3RC)) && + !(df_attributes & DF_NON_NULL_RET)) { + uses_method_star &= InvokeUsesMethodStar(mir); + } + if (uses_method_star) { + raw_use_counts_.Increment(method_sreg_); + use_counts_.Put(method_sreg_, use_counts_.Get(method_sreg_) + (1 << weight)); + } + } + } + } + return false; +} + +void MIRGraph::MethodUseCount() +{ + // Now that we know, resize the lists. + int num_ssa_regs = GetNumSSARegs(); + use_counts_.Resize(num_ssa_regs + 32); + raw_use_counts_.Resize(num_ssa_regs + 32); + // Initialize list + for (int i = 0; i < num_ssa_regs; i++) { + use_counts_.Insert(0); + raw_use_counts_.Insert(0); + } + if (cu_->disable_opt & (1 << kPromoteRegs)) { + return; + } + AllNodesIterator iter(this, false /* not iterative */); + for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { + CountUses(bb); + } +} + +/* Verify if all the successor is connected with all the claimed predecessors */ +bool MIRGraph::VerifyPredInfo(BasicBlock* bb) +{ + GrowableArray::Iterator iter(bb->predecessors); + + while (true) { + BasicBlock *pred_bb = iter.Next(); + if (!pred_bb) break; + bool found = false; + if (pred_bb->taken == bb) { + found = true; + } else if (pred_bb->fall_through == bb) { + found = true; + } else if (pred_bb->successor_block_list.block_list_type != kNotUsed) { + GrowableArray::Iterator iterator(pred_bb->successor_block_list.blocks); + while (true) { + SuccessorBlockInfo *successor_block_info = iterator.Next(); + if (successor_block_info == NULL) break; + BasicBlock *succ_bb = successor_block_info->block; + if (succ_bb == bb) { + found = true; + break; + } + } + } + if (found == false) { + char block_name1[BLOCK_NAME_LEN], block_name2[BLOCK_NAME_LEN]; + GetBlockName(bb, block_name1); + GetBlockName(pred_bb, block_name2); + DumpCFG("/sdcard/cfg/", false); + LOG(FATAL) << "Successor " << block_name1 << "not found from " + << block_name2; + } + } + return true; +} + +void MIRGraph::VerifyDataflow() +{ + /* Verify if all blocks are connected as claimed */ + AllNodesIterator iter(this, false /* not iterative */); + for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { + VerifyPredInfo(bb); + } +} + +} // namespace art diff --git a/src/compiler/dex/mir_graph.cc b/src/compiler/dex/mir_graph.cc new file mode 100644 index 0000000000000000000000000000000000000000..6154eec6ca13ae7545e72a4054337410939d80cd --- /dev/null +++ b/src/compiler/dex/mir_graph.cc @@ -0,0 +1,1178 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/stl_util.h" +#include "compiler_internals.h" +#include "dex_file-inl.h" +#include "leb128.h" +#include "mir_graph.h" + +namespace art { + +#define MAX_PATTERN_LEN 5 + +struct CodePattern { + const Instruction::Code opcodes[MAX_PATTERN_LEN]; + const SpecialCaseHandler handler_code; +}; + +static const CodePattern special_patterns[] = { + {{Instruction::RETURN_VOID}, kNullMethod}, + {{Instruction::CONST, Instruction::RETURN}, kConstFunction}, + {{Instruction::CONST_4, Instruction::RETURN}, kConstFunction}, + {{Instruction::CONST_4, Instruction::RETURN_OBJECT}, kConstFunction}, + {{Instruction::CONST_16, Instruction::RETURN}, kConstFunction}, + {{Instruction::IGET, Instruction:: RETURN}, kIGet}, + {{Instruction::IGET_BOOLEAN, Instruction::RETURN}, kIGetBoolean}, + {{Instruction::IGET_OBJECT, Instruction::RETURN_OBJECT}, kIGetObject}, + {{Instruction::IGET_BYTE, Instruction::RETURN}, kIGetByte}, + {{Instruction::IGET_CHAR, Instruction::RETURN}, kIGetChar}, + {{Instruction::IGET_SHORT, Instruction::RETURN}, kIGetShort}, + {{Instruction::IGET_WIDE, Instruction::RETURN_WIDE}, kIGetWide}, + {{Instruction::IPUT, Instruction::RETURN_VOID}, kIPut}, + {{Instruction::IPUT_BOOLEAN, Instruction::RETURN_VOID}, kIPutBoolean}, + {{Instruction::IPUT_OBJECT, Instruction::RETURN_VOID}, kIPutObject}, + {{Instruction::IPUT_BYTE, Instruction::RETURN_VOID}, kIPutByte}, + {{Instruction::IPUT_CHAR, Instruction::RETURN_VOID}, kIPutChar}, + {{Instruction::IPUT_SHORT, Instruction::RETURN_VOID}, kIPutShort}, + {{Instruction::IPUT_WIDE, Instruction::RETURN_VOID}, kIPutWide}, + {{Instruction::RETURN}, kIdentity}, + {{Instruction::RETURN_OBJECT}, kIdentity}, + {{Instruction::RETURN_WIDE}, kIdentity}, +}; + +const char* MIRGraph::extended_mir_op_names_[kMirOpLast - kMirOpFirst] = { + "Phi", + "Copy", + "FusedCmplFloat", + "FusedCmpgFloat", + "FusedCmplDouble", + "FusedCmpgDouble", + "FusedCmpLong", + "Nop", + "OpNullCheck", + "OpRangeCheck", + "OpDivZeroCheck", + "Check1", + "Check2", + "Select", +}; + +MIRGraph::MIRGraph(CompilationUnit* cu, ArenaAllocator* arena) + : reg_location_(NULL), + compiler_temps_(arena, 6, kGrowableArrayMisc), + cu_(cu), + ssa_base_vregs_(NULL), + ssa_subscripts_(NULL), + ssa_strings_(NULL), + vreg_to_ssa_map_(NULL), + ssa_last_defs_(NULL), + is_constant_v_(NULL), + constant_values_(NULL), + use_counts_(arena, 256, kGrowableArrayMisc), + raw_use_counts_(arena, 256, kGrowableArrayMisc), + num_reachable_blocks_(0), + dfs_order_(NULL), + dfs_post_order_(NULL), + dom_post_order_traversal_(NULL), + i_dom_list_(NULL), + def_block_matrix_(NULL), + temp_block_v_(NULL), + temp_dalvik_register_v_(NULL), + temp_ssa_register_v_(NULL), + block_list_(arena, 100, kGrowableArrayBlockList), + try_block_addr_(NULL), + entry_block_(NULL), + exit_block_(NULL), + cur_block_(NULL), + num_blocks_(0), + current_code_item_(NULL), + current_method_(kInvalidEntry), + current_offset_(kInvalidEntry), + def_count_(0), + opcode_count_(NULL), + num_ssa_regs_(0), + method_sreg_(0), + attributes_(METHOD_IS_LEAF), // Start with leaf assumption, change on encountering invoke. + checkstats_(NULL), + arena_(arena) + { + try_block_addr_ = new (arena_) ArenaBitVector(arena_, 0, true /* expandable */); +} + +MIRGraph::~MIRGraph() { + STLDeleteElements(&m_units_); +} + +bool MIRGraph::ContentIsInsn(const uint16_t* code_ptr) { + uint16_t instr = *code_ptr; + Instruction::Code opcode = static_cast(instr & 0xff); + /* + * Since the low 8-bit in metadata may look like NOP, we need to check + * both the low and whole sub-word to determine whether it is code or data. + */ + return (opcode != Instruction::NOP || instr == 0); +} + +/* + * Parse an instruction, return the length of the instruction + */ +int MIRGraph::ParseInsn(const uint16_t* code_ptr, DecodedInstruction* decoded_instruction) +{ + // Don't parse instruction data + if (!ContentIsInsn(code_ptr)) { + return 0; + } + + const Instruction* instruction = Instruction::At(code_ptr); + *decoded_instruction = DecodedInstruction(instruction); + + return instruction->SizeInCodeUnits(); +} + + +/* Split an existing block from the specified code offset into two */ +BasicBlock* MIRGraph::SplitBlock(unsigned int code_offset, + BasicBlock* orig_block, BasicBlock** immed_pred_block_p) +{ + MIR* insn = orig_block->first_mir_insn; + while (insn) { + if (insn->offset == code_offset) break; + insn = insn->next; + } + if (insn == NULL) { + LOG(FATAL) << "Break split failed"; + } + BasicBlock *bottom_block = NewMemBB(kDalvikByteCode, num_blocks_++); + block_list_.Insert(bottom_block); + + bottom_block->start_offset = code_offset; + bottom_block->first_mir_insn = insn; + bottom_block->last_mir_insn = orig_block->last_mir_insn; + + /* If this block was terminated by a return, the flag needs to go with the bottom block */ + bottom_block->terminated_by_return = orig_block->terminated_by_return; + orig_block->terminated_by_return = false; + + /* Add it to the quick lookup cache */ + block_map_.Put(bottom_block->start_offset, bottom_block); + + /* Handle the taken path */ + bottom_block->taken = orig_block->taken; + if (bottom_block->taken) { + orig_block->taken = NULL; + bottom_block->taken->predecessors->Delete(orig_block); + bottom_block->taken->predecessors->Insert(bottom_block); + } + + /* Handle the fallthrough path */ + bottom_block->fall_through = orig_block->fall_through; + orig_block->fall_through = bottom_block; + bottom_block->predecessors->Insert(orig_block); + if (bottom_block->fall_through) { + bottom_block->fall_through->predecessors->Delete(orig_block); + bottom_block->fall_through->predecessors->Insert(bottom_block); + } + + /* Handle the successor list */ + if (orig_block->successor_block_list.block_list_type != kNotUsed) { + bottom_block->successor_block_list = orig_block->successor_block_list; + orig_block->successor_block_list.block_list_type = kNotUsed; + GrowableArray::Iterator iterator(bottom_block->successor_block_list.blocks); + while (true) { + SuccessorBlockInfo *successor_block_info = iterator.Next(); + if (successor_block_info == NULL) break; + BasicBlock *bb = successor_block_info->block; + bb->predecessors->Delete(orig_block); + bb->predecessors->Insert(bottom_block); + } + } + + orig_block->last_mir_insn = insn->prev; + + insn->prev->next = NULL; + insn->prev = NULL; + /* + * Update the immediate predecessor block pointer so that outgoing edges + * can be applied to the proper block. + */ + if (immed_pred_block_p) { + DCHECK_EQ(*immed_pred_block_p, orig_block); + *immed_pred_block_p = bottom_block; + } + return bottom_block; +} + +/* + * Given a code offset, find out the block that starts with it. If the offset + * is in the middle of an existing block, split it into two. If immed_pred_block_p + * is not non-null and is the block being split, update *immed_pred_block_p to + * point to the bottom block so that outgoing edges can be set up properly + * (by the caller) + * Utilizes a map for fast lookup of the typical cases. + */ +BasicBlock* MIRGraph::FindBlock(unsigned int code_offset, bool split, bool create, + BasicBlock** immed_pred_block_p) +{ + BasicBlock* bb; + unsigned int i; + SafeMap::iterator it; + + it = block_map_.find(code_offset); + if (it != block_map_.end()) { + return it->second; + } else if (!create) { + return NULL; + } + + if (split) { + for (i = 0; i < block_list_.Size(); i++) { + bb = block_list_.Get(i); + if (bb->block_type != kDalvikByteCode) continue; + /* Check if a branch jumps into the middle of an existing block */ + if ((code_offset > bb->start_offset) && (bb->last_mir_insn != NULL) && + (code_offset <= bb->last_mir_insn->offset)) { + BasicBlock *new_bb = SplitBlock(code_offset, bb, bb == *immed_pred_block_p ? + immed_pred_block_p : NULL); + return new_bb; + } + } + } + + /* Create a new one */ + bb = NewMemBB(kDalvikByteCode, num_blocks_++); + block_list_.Insert(bb); + bb->start_offset = code_offset; + block_map_.Put(bb->start_offset, bb); + return bb; +} + +/* Identify code range in try blocks and set up the empty catch blocks */ +void MIRGraph::ProcessTryCatchBlocks() +{ + int tries_size = current_code_item_->tries_size_; + int offset; + + if (tries_size == 0) { + return; + } + + for (int i = 0; i < tries_size; i++) { + const DexFile::TryItem* pTry = + DexFile::GetTryItems(*current_code_item_, i); + int start_offset = pTry->start_addr_; + int end_offset = start_offset + pTry->insn_count_; + for (offset = start_offset; offset < end_offset; offset++) { + try_block_addr_->SetBit(offset); + } + } + + // Iterate over each of the handlers to enqueue the empty Catch blocks + const byte* handlers_ptr = DexFile::GetCatchHandlerData(*current_code_item_, 0); + uint32_t handlers_size = DecodeUnsignedLeb128(&handlers_ptr); + for (uint32_t idx = 0; idx < handlers_size; idx++) { + CatchHandlerIterator iterator(handlers_ptr); + for (; iterator.HasNext(); iterator.Next()) { + uint32_t address = iterator.GetHandlerAddress(); + FindBlock(address, false /* split */, true /*create*/, + /* immed_pred_block_p */ NULL); + } + handlers_ptr = iterator.EndDataPointer(); + } +} + +/* Process instructions with the kBranch flag */ +BasicBlock* MIRGraph::ProcessCanBranch(BasicBlock* cur_block, MIR* insn, int cur_offset, int width, + int flags, const uint16_t* code_ptr, + const uint16_t* code_end) +{ + int target = cur_offset; + switch (insn->dalvikInsn.opcode) { + case Instruction::GOTO: + case Instruction::GOTO_16: + case Instruction::GOTO_32: + target += insn->dalvikInsn.vA; + break; + case Instruction::IF_EQ: + case Instruction::IF_NE: + case Instruction::IF_LT: + case Instruction::IF_GE: + case Instruction::IF_GT: + case Instruction::IF_LE: + cur_block->conditional_branch = true; + target += insn->dalvikInsn.vC; + break; + case Instruction::IF_EQZ: + case Instruction::IF_NEZ: + case Instruction::IF_LTZ: + case Instruction::IF_GEZ: + case Instruction::IF_GTZ: + case Instruction::IF_LEZ: + cur_block->conditional_branch = true; + target += insn->dalvikInsn.vB; + break; + default: + LOG(FATAL) << "Unexpected opcode(" << insn->dalvikInsn.opcode << ") with kBranch set"; + } + BasicBlock *taken_block = FindBlock(target, /* split */ true, /* create */ true, + /* immed_pred_block_p */ &cur_block); + cur_block->taken = taken_block; + taken_block->predecessors->Insert(cur_block); + + /* Always terminate the current block for conditional branches */ + if (flags & Instruction::kContinue) { + BasicBlock *fallthrough_block = FindBlock(cur_offset + width, + /* + * If the method is processed + * in sequential order from the + * beginning, we don't need to + * specify split for continue + * blocks. However, this + * routine can be called by + * compileLoop, which starts + * parsing the method from an + * arbitrary address in the + * method body. + */ + true, + /* create */ + true, + /* immed_pred_block_p */ + &cur_block); + cur_block->fall_through = fallthrough_block; + fallthrough_block->predecessors->Insert(cur_block); + } else if (code_ptr < code_end) { + /* Create a fallthrough block for real instructions (incl. NOP) */ + if (ContentIsInsn(code_ptr)) { + FindBlock(cur_offset + width, /* split */ false, /* create */ true, + /* immed_pred_block_p */ NULL); + } + } + return cur_block; +} + +/* Process instructions with the kSwitch flag */ +void MIRGraph::ProcessCanSwitch(BasicBlock* cur_block, MIR* insn, int cur_offset, int width, + int flags) +{ + const uint16_t* switch_data = + reinterpret_cast(GetCurrentInsns() + cur_offset + insn->dalvikInsn.vB); + int size; + const int* keyTable; + const int* target_table; + int i; + int first_key; + + /* + * Packed switch data format: + * ushort ident = 0x0100 magic value + * ushort size number of entries in the table + * int first_key first (and lowest) switch case value + * int targets[size] branch targets, relative to switch opcode + * + * Total size is (4+size*2) 16-bit code units. + */ + if (insn->dalvikInsn.opcode == Instruction::PACKED_SWITCH) { + DCHECK_EQ(static_cast(switch_data[0]), + static_cast(Instruction::kPackedSwitchSignature)); + size = switch_data[1]; + first_key = switch_data[2] | (switch_data[3] << 16); + target_table = reinterpret_cast(&switch_data[4]); + keyTable = NULL; // Make the compiler happy + /* + * Sparse switch data format: + * ushort ident = 0x0200 magic value + * ushort size number of entries in the table; > 0 + * int keys[size] keys, sorted low-to-high; 32-bit aligned + * int targets[size] branch targets, relative to switch opcode + * + * Total size is (2+size*4) 16-bit code units. + */ + } else { + DCHECK_EQ(static_cast(switch_data[0]), + static_cast(Instruction::kSparseSwitchSignature)); + size = switch_data[1]; + keyTable = reinterpret_cast(&switch_data[2]); + target_table = reinterpret_cast(&switch_data[2 + size*2]); + first_key = 0; // To make the compiler happy + } + + if (cur_block->successor_block_list.block_list_type != kNotUsed) { + LOG(FATAL) << "Successor block list already in use: " + << static_cast(cur_block->successor_block_list.block_list_type); + } + cur_block->successor_block_list.block_list_type = + (insn->dalvikInsn.opcode == Instruction::PACKED_SWITCH) ? + kPackedSwitch : kSparseSwitch; + cur_block->successor_block_list.blocks = + new (arena_)GrowableArray(arena_, size, kGrowableArraySuccessorBlocks); + + for (i = 0; i < size; i++) { + BasicBlock *case_block = FindBlock(cur_offset + target_table[i], /* split */ true, + /* create */ true, /* immed_pred_block_p */ &cur_block); + SuccessorBlockInfo *successor_block_info = + static_cast(arena_->NewMem(sizeof(SuccessorBlockInfo), false, + ArenaAllocator::kAllocSuccessor)); + successor_block_info->block = case_block; + successor_block_info->key = + (insn->dalvikInsn.opcode == Instruction::PACKED_SWITCH) ? + first_key + i : keyTable[i]; + cur_block->successor_block_list.blocks->Insert(successor_block_info); + case_block->predecessors->Insert(cur_block); + } + + /* Fall-through case */ + BasicBlock* fallthrough_block = FindBlock( cur_offset + width, /* split */ false, + /* create */ true, /* immed_pred_block_p */ NULL); + cur_block->fall_through = fallthrough_block; + fallthrough_block->predecessors->Insert(cur_block); +} + +/* Process instructions with the kThrow flag */ +BasicBlock* MIRGraph::ProcessCanThrow(BasicBlock* cur_block, MIR* insn, int cur_offset, int width, + int flags, ArenaBitVector* try_block_addr, + const uint16_t* code_ptr, const uint16_t* code_end) +{ + bool in_try_block = try_block_addr->IsBitSet(cur_offset); + + /* In try block */ + if (in_try_block) { + CatchHandlerIterator iterator(*current_code_item_, cur_offset); + + if (cur_block->successor_block_list.block_list_type != kNotUsed) { + LOG(INFO) << PrettyMethod(cu_->method_idx, *cu_->dex_file); + LOG(FATAL) << "Successor block list already in use: " + << static_cast(cur_block->successor_block_list.block_list_type); + } + + cur_block->successor_block_list.block_list_type = kCatch; + cur_block->successor_block_list.blocks = + new (arena_) GrowableArray(arena_, 2, kGrowableArraySuccessorBlocks); + + for (;iterator.HasNext(); iterator.Next()) { + BasicBlock *catch_block = FindBlock(iterator.GetHandlerAddress(), false /* split*/, + false /* creat */, NULL /* immed_pred_block_p */); + catch_block->catch_entry = true; + if (kIsDebugBuild) { + catches_.insert(catch_block->start_offset); + } + SuccessorBlockInfo *successor_block_info = reinterpret_cast + (arena_->NewMem(sizeof(SuccessorBlockInfo), false, ArenaAllocator::kAllocSuccessor)); + successor_block_info->block = catch_block; + successor_block_info->key = iterator.GetHandlerTypeIndex(); + cur_block->successor_block_list.blocks->Insert(successor_block_info); + catch_block->predecessors->Insert(cur_block); + } + } else { + BasicBlock *eh_block = NewMemBB(kExceptionHandling, num_blocks_++); + cur_block->taken = eh_block; + block_list_.Insert(eh_block); + eh_block->start_offset = cur_offset; + eh_block->predecessors->Insert(cur_block); + } + + if (insn->dalvikInsn.opcode == Instruction::THROW){ + cur_block->explicit_throw = true; + if ((code_ptr < code_end) && ContentIsInsn(code_ptr)) { + // Force creation of new block following THROW via side-effect + FindBlock(cur_offset + width, /* split */ false, /* create */ true, + /* immed_pred_block_p */ NULL); + } + if (!in_try_block) { + // Don't split a THROW that can't rethrow - we're done. + return cur_block; + } + } + + /* + * Split the potentially-throwing instruction into two parts. + * The first half will be a pseudo-op that captures the exception + * edges and terminates the basic block. It always falls through. + * Then, create a new basic block that begins with the throwing instruction + * (minus exceptions). Note: this new basic block must NOT be entered into + * the block_map. If the potentially-throwing instruction is the target of a + * future branch, we need to find the check psuedo half. The new + * basic block containing the work portion of the instruction should + * only be entered via fallthrough from the block containing the + * pseudo exception edge MIR. Note also that this new block is + * not automatically terminated after the work portion, and may + * contain following instructions. + */ + BasicBlock *new_block = NewMemBB(kDalvikByteCode, num_blocks_++); + block_list_.Insert(new_block); + new_block->start_offset = insn->offset; + cur_block->fall_through = new_block; + new_block->predecessors->Insert(cur_block); + MIR* new_insn = static_cast(arena_->NewMem(sizeof(MIR), true, ArenaAllocator::kAllocMIR)); + *new_insn = *insn; + insn->dalvikInsn.opcode = + static_cast(kMirOpCheck); + // Associate the two halves + insn->meta.throw_insn = new_insn; + new_insn->meta.throw_insn = insn; + AppendMIR(new_block, new_insn); + return new_block; +} + +/* Parse a Dex method and insert it into the MIRGraph at the current insert point. */ +void MIRGraph::InlineMethod(const DexFile::CodeItem* code_item, uint32_t access_flags, + InvokeType invoke_type, uint32_t class_def_idx, + uint32_t method_idx, jobject class_loader, const DexFile& dex_file) +{ + current_code_item_ = code_item; + method_stack_.push_back(std::make_pair(current_method_, current_offset_)); + current_method_ = m_units_.size(); + current_offset_ = 0; + // TODO: will need to snapshot stack image and use that as the mir context identification. + m_units_.push_back(new DexCompilationUnit(cu_, class_loader, Runtime::Current()->GetClassLinker(), + dex_file, current_code_item_, class_def_idx, method_idx, access_flags)); + const uint16_t* code_ptr = current_code_item_->insns_; + const uint16_t* code_end = + current_code_item_->insns_ + current_code_item_->insns_size_in_code_units_; + + // TODO: need to rework expansion of block list & try_block_addr when inlining activated. + block_list_.Resize(block_list_.Size() + current_code_item_->insns_size_in_code_units_); + // TODO: replace with explicit resize routine. Using automatic extension side effect for now. + try_block_addr_->SetBit(current_code_item_->insns_size_in_code_units_); + try_block_addr_->ClearBit(current_code_item_->insns_size_in_code_units_); + + // If this is the first method, set up default entry and exit blocks. + if (current_method_ == 0) { + DCHECK(entry_block_ == NULL); + DCHECK(exit_block_ == NULL); + DCHECK(num_blocks_ == 0); + entry_block_ = NewMemBB(kEntryBlock, num_blocks_++); + exit_block_ = NewMemBB(kExitBlock, num_blocks_++); + block_list_.Insert(entry_block_); + block_list_.Insert(exit_block_); + // TODO: deprecate all "cu->" fields; move what's left to wherever CompilationUnit is allocated. + cu_->dex_file = &dex_file; + cu_->class_def_idx = class_def_idx; + cu_->method_idx = method_idx; + cu_->access_flags = access_flags; + cu_->invoke_type = invoke_type; + cu_->shorty = dex_file.GetMethodShorty(dex_file.GetMethodId(method_idx)); + cu_->num_ins = current_code_item_->ins_size_; + cu_->num_regs = current_code_item_->registers_size_ - cu_->num_ins; + cu_->num_outs = current_code_item_->outs_size_; + cu_->num_dalvik_registers = current_code_item_->registers_size_; + cu_->insns = current_code_item_->insns_; + cu_->code_item = current_code_item_; + } else { + UNIMPLEMENTED(FATAL) << "Nested inlining not implemented."; + /* + * Will need to manage storage for ins & outs, push prevous state and update + * insert point. + */ + } + + /* Current block to record parsed instructions */ + BasicBlock *cur_block = NewMemBB(kDalvikByteCode, num_blocks_++); + DCHECK_EQ(current_offset_, 0); + cur_block->start_offset = current_offset_; + block_list_.Insert(cur_block); + /* Add first block to the fast lookup cache */ +// FIXME: block map needs association with offset/method pair rather than just offset + block_map_.Put(cur_block->start_offset, cur_block); +// FIXME: this needs to insert at the insert point rather than entry block. + entry_block_->fall_through = cur_block; + cur_block->predecessors->Insert(entry_block_); + + /* Identify code range in try blocks and set up the empty catch blocks */ + ProcessTryCatchBlocks(); + + /* Set up for simple method detection */ + int num_patterns = sizeof(special_patterns)/sizeof(special_patterns[0]); + bool live_pattern = (num_patterns > 0) && !(cu_->disable_opt & (1 << kMatch)); + bool* dead_pattern = + static_cast(arena_->NewMem(sizeof(bool) * num_patterns, true, + ArenaAllocator::kAllocMisc)); + SpecialCaseHandler special_case = kNoHandler; + // FIXME - wire this up + (void)special_case; + int pattern_pos = 0; + + /* Parse all instructions and put them into containing basic blocks */ + while (code_ptr < code_end) { + MIR *insn = static_cast(arena_->NewMem(sizeof(MIR), true, ArenaAllocator::kAllocMIR)); + insn->offset = current_offset_; + insn->m_unit_index = current_method_; + int width = ParseInsn(code_ptr, &insn->dalvikInsn); + insn->width = width; + Instruction::Code opcode = insn->dalvikInsn.opcode; + if (opcode_count_ != NULL) { + opcode_count_[static_cast(opcode)]++; + } + + /* Terminate when the data section is seen */ + if (width == 0) + break; + + /* Possible simple method? */ + if (live_pattern) { + live_pattern = false; + special_case = kNoHandler; + for (int i = 0; i < num_patterns; i++) { + if (!dead_pattern[i]) { + if (special_patterns[i].opcodes[pattern_pos] == opcode) { + live_pattern = true; + special_case = special_patterns[i].handler_code; + } else { + dead_pattern[i] = true; + } + } + } + pattern_pos++; + } + + AppendMIR(cur_block, insn); + + code_ptr += width; + int flags = Instruction::FlagsOf(insn->dalvikInsn.opcode); + + int df_flags = oat_data_flow_attributes_[insn->dalvikInsn.opcode]; + + if (df_flags & DF_HAS_DEFS) { + def_count_ += (df_flags & DF_A_WIDE) ? 2 : 1; + } + + if (flags & Instruction::kBranch) { + cur_block = ProcessCanBranch(cur_block, insn, current_offset_, + width, flags, code_ptr, code_end); + } else if (flags & Instruction::kReturn) { + cur_block->terminated_by_return = true; + cur_block->fall_through = exit_block_; + exit_block_->predecessors->Insert(cur_block); + /* + * Terminate the current block if there are instructions + * afterwards. + */ + if (code_ptr < code_end) { + /* + * Create a fallthrough block for real instructions + * (incl. NOP). + */ + if (ContentIsInsn(code_ptr)) { + FindBlock(current_offset_ + width, /* split */ false, /* create */ true, + /* immed_pred_block_p */ NULL); + } + } + } else if (flags & Instruction::kThrow) { + cur_block = ProcessCanThrow(cur_block, insn, current_offset_, width, flags, try_block_addr_, + code_ptr, code_end); + } else if (flags & Instruction::kSwitch) { + ProcessCanSwitch(cur_block, insn, current_offset_, width, flags); + } + current_offset_ += width; + BasicBlock *next_block = FindBlock(current_offset_, /* split */ false, /* create */ + false, /* immed_pred_block_p */ NULL); + if (next_block) { + /* + * The next instruction could be the target of a previously parsed + * forward branch so a block is already created. If the current + * instruction is not an unconditional branch, connect them through + * the fall-through link. + */ + DCHECK(cur_block->fall_through == NULL || + cur_block->fall_through == next_block || + cur_block->fall_through == exit_block_); + + if ((cur_block->fall_through == NULL) && (flags & Instruction::kContinue)) { + cur_block->fall_through = next_block; + next_block->predecessors->Insert(cur_block); + } + cur_block = next_block; + } + } + if (cu_->enable_debug & (1 << kDebugDumpCFG)) { + DumpCFG("/sdcard/1_post_parse_cfg/", true); + } + + if (cu_->verbose) { + DumpMIRGraph(); + } +} + +void MIRGraph::ShowOpcodeStats() +{ + DCHECK(opcode_count_ != NULL); + LOG(INFO) << "Opcode Count"; + for (int i = 0; i < kNumPackedOpcodes; i++) { + if (opcode_count_[i] != 0) { + LOG(INFO) << "-C- " << Instruction::Name(static_cast(i)) + << " " << opcode_count_[i]; + } + } +} + +// TODO: use a configurable base prefix, and adjust callers to supply pass name. +/* Dump the CFG into a DOT graph */ +void MIRGraph::DumpCFG(const char* dir_prefix, bool all_blocks) +{ + FILE* file; + std::string fname(PrettyMethod(cu_->method_idx, *cu_->dex_file)); + ReplaceSpecialChars(fname); + fname = StringPrintf("%s%s%x.dot", dir_prefix, fname.c_str(), + GetEntryBlock()->fall_through->start_offset); + file = fopen(fname.c_str(), "w"); + if (file == NULL) { + return; + } + fprintf(file, "digraph G {\n"); + + fprintf(file, " rankdir=TB\n"); + + int num_blocks = all_blocks ? GetNumBlocks() : num_reachable_blocks_; + int idx; + + for (idx = 0; idx < num_blocks; idx++) { + int block_idx = all_blocks ? idx : dfs_order_->Get(idx); + BasicBlock *bb = GetBasicBlock(block_idx); + if (bb == NULL) break; + if (bb->block_type == kDead) continue; + if (bb->block_type == kEntryBlock) { + fprintf(file, " entry_%d [shape=Mdiamond];\n", bb->id); + } else if (bb->block_type == kExitBlock) { + fprintf(file, " exit_%d [shape=Mdiamond];\n", bb->id); + } else if (bb->block_type == kDalvikByteCode) { + fprintf(file, " block%04x_%d [shape=record,label = \"{ \\\n", + bb->start_offset, bb->id); + const MIR *mir; + fprintf(file, " {block id %d\\l}%s\\\n", bb->id, + bb->first_mir_insn ? " | " : " "); + for (mir = bb->first_mir_insn; mir; mir = mir->next) { + int opcode = mir->dalvikInsn.opcode; + fprintf(file, " {%04x %s %s %s\\l}%s\\\n", mir->offset, + mir->ssa_rep ? GetDalvikDisassembly(mir) : + (opcode < kMirOpFirst) ? Instruction::Name(mir->dalvikInsn.opcode) : + extended_mir_op_names_[opcode - kMirOpFirst], + (mir->optimization_flags & MIR_IGNORE_RANGE_CHECK) != 0 ? " no_rangecheck" : " ", + (mir->optimization_flags & MIR_IGNORE_NULL_CHECK) != 0 ? " no_nullcheck" : " ", + mir->next ? " | " : " "); + } + fprintf(file, " }\"];\n\n"); + } else if (bb->block_type == kExceptionHandling) { + char block_name[BLOCK_NAME_LEN]; + + GetBlockName(bb, block_name); + fprintf(file, " %s [shape=invhouse];\n", block_name); + } + + char block_name1[BLOCK_NAME_LEN], block_name2[BLOCK_NAME_LEN]; + + if (bb->taken) { + GetBlockName(bb, block_name1); + GetBlockName(bb->taken, block_name2); + fprintf(file, " %s:s -> %s:n [style=dotted]\n", + block_name1, block_name2); + } + if (bb->fall_through) { + GetBlockName(bb, block_name1); + GetBlockName(bb->fall_through, block_name2); + fprintf(file, " %s:s -> %s:n\n", block_name1, block_name2); + } + + if (bb->successor_block_list.block_list_type != kNotUsed) { + fprintf(file, " succ%04x_%d [shape=%s,label = \"{ \\\n", + bb->start_offset, bb->id, + (bb->successor_block_list.block_list_type == kCatch) ? + "Mrecord" : "record"); + GrowableArray::Iterator iterator(bb->successor_block_list.blocks); + SuccessorBlockInfo *successor_block_info = iterator.Next(); + + int succ_id = 0; + while (true) { + if (successor_block_info == NULL) break; + + BasicBlock *dest_block = successor_block_info->block; + SuccessorBlockInfo *next_successor_block_info = iterator.Next(); + + fprintf(file, " { %04x: %04x\\l}%s\\\n", + succ_id++, + successor_block_info->key, + dest_block->start_offset, + (next_successor_block_info != NULL) ? " | " : " "); + + successor_block_info = next_successor_block_info; + } + fprintf(file, " }\"];\n\n"); + + GetBlockName(bb, block_name1); + fprintf(file, " %s:s -> succ%04x_%d:n [style=dashed]\n", + block_name1, bb->start_offset, bb->id); + + if (bb->successor_block_list.block_list_type == kPackedSwitch || + bb->successor_block_list.block_list_type == kSparseSwitch) { + + GrowableArray::Iterator iter(bb->successor_block_list.blocks); + + succ_id = 0; + while (true) { + SuccessorBlockInfo *successor_block_info = iter.Next(); + if (successor_block_info == NULL) break; + + BasicBlock *dest_block = successor_block_info->block; + + GetBlockName(dest_block, block_name2); + fprintf(file, " succ%04x_%d:f%d:e -> %s:n\n", bb->start_offset, + bb->id, succ_id++, block_name2); + } + } + } + fprintf(file, "\n"); + + if (cu_->verbose) { + /* Display the dominator tree */ + GetBlockName(bb, block_name1); + fprintf(file, " cfg%s [label=\"%s\", shape=none];\n", + block_name1, block_name1); + if (bb->i_dom) { + GetBlockName(bb->i_dom, block_name2); + fprintf(file, " cfg%s:s -> cfg%s:n\n\n", block_name2, block_name1); + } + } + } + fprintf(file, "}\n"); + fclose(file); +} + +/* Insert an MIR instruction to the end of a basic block */ +void MIRGraph::AppendMIR(BasicBlock* bb, MIR* mir) +{ + if (bb->first_mir_insn == NULL) { + DCHECK(bb->last_mir_insn == NULL); + bb->last_mir_insn = bb->first_mir_insn = mir; + mir->prev = mir->next = NULL; + } else { + bb->last_mir_insn->next = mir; + mir->prev = bb->last_mir_insn; + mir->next = NULL; + bb->last_mir_insn = mir; + } +} + +/* Insert an MIR instruction to the head of a basic block */ +void MIRGraph::PrependMIR(BasicBlock* bb, MIR* mir) +{ + if (bb->first_mir_insn == NULL) { + DCHECK(bb->last_mir_insn == NULL); + bb->last_mir_insn = bb->first_mir_insn = mir; + mir->prev = mir->next = NULL; + } else { + bb->first_mir_insn->prev = mir; + mir->next = bb->first_mir_insn; + mir->prev = NULL; + bb->first_mir_insn = mir; + } +} + +/* Insert a MIR instruction after the specified MIR */ +void MIRGraph::InsertMIRAfter(BasicBlock* bb, MIR* current_mir, MIR* new_mir) +{ + new_mir->prev = current_mir; + new_mir->next = current_mir->next; + current_mir->next = new_mir; + + if (new_mir->next) { + /* Is not the last MIR in the block */ + new_mir->next->prev = new_mir; + } else { + /* Is the last MIR in the block */ + bb->last_mir_insn = new_mir; + } +} + +char* MIRGraph::GetDalvikDisassembly(const MIR* mir) +{ + DecodedInstruction insn = mir->dalvikInsn; + std::string str; + int flags = 0; + int opcode = insn.opcode; + char* ret; + bool nop = false; + SSARepresentation* ssa_rep = mir->ssa_rep; + Instruction::Format dalvik_format = Instruction::k10x; // Default to no-operand format + int defs = (ssa_rep != NULL) ? ssa_rep->num_defs : 0; + int uses = (ssa_rep != NULL) ? ssa_rep->num_uses : 0; + + // Handle special cases. + if ((opcode == kMirOpCheck) || (opcode == kMirOpCheckPart2)) { + str.append(extended_mir_op_names_[opcode - kMirOpFirst]); + str.append(": "); + // Recover the original Dex instruction + insn = mir->meta.throw_insn->dalvikInsn; + ssa_rep = mir->meta.throw_insn->ssa_rep; + defs = ssa_rep->num_defs; + uses = ssa_rep->num_uses; + opcode = insn.opcode; + } else if (opcode == kMirOpNop) { + str.append("["); + insn.opcode = mir->meta.original_opcode; + opcode = mir->meta.original_opcode; + nop = true; + } + + if (opcode >= kMirOpFirst) { + str.append(extended_mir_op_names_[opcode - kMirOpFirst]); + } else { + dalvik_format = Instruction::FormatOf(insn.opcode); + flags = Instruction::FlagsOf(insn.opcode); + str.append(Instruction::Name(insn.opcode)); + } + + if (opcode == kMirOpPhi) { + int* incoming = reinterpret_cast(insn.vB); + str.append(StringPrintf(" %s = (%s", + GetSSANameWithConst(ssa_rep->defs[0], true).c_str(), + GetSSANameWithConst(ssa_rep->uses[0], true).c_str())); + str.append(StringPrintf(":%d",incoming[0])); + int i; + for (i = 1; i < uses; i++) { + str.append(StringPrintf(", %s:%d", + GetSSANameWithConst(ssa_rep->uses[i], true).c_str(), + incoming[i])); + } + str.append(")"); + } else if ((flags & Instruction::kBranch) != 0) { + // For branches, decode the instructions to print out the branch targets. + int offset = 0; + switch (dalvik_format) { + case Instruction::k21t: + str.append(StringPrintf(" %s,", GetSSANameWithConst(ssa_rep->uses[0], false).c_str())); + offset = insn.vB; + break; + case Instruction::k22t: + str.append(StringPrintf(" %s, %s,", GetSSANameWithConst(ssa_rep->uses[0], false).c_str(), + GetSSANameWithConst(ssa_rep->uses[1], false).c_str())); + offset = insn.vC; + break; + case Instruction::k10t: + case Instruction::k20t: + case Instruction::k30t: + offset = insn.vA; + break; + default: + LOG(FATAL) << "Unexpected branch format " << dalvik_format << " from " << insn.opcode; + } + str.append(StringPrintf(" 0x%x (%c%x)", mir->offset + offset, + offset > 0 ? '+' : '-', offset > 0 ? offset : -offset)); + } else { + // For invokes-style formats, treat wide regs as a pair of singles + bool show_singles = ((dalvik_format == Instruction::k35c) || + (dalvik_format == Instruction::k3rc)); + if (defs != 0) { + str.append(StringPrintf(" %s", GetSSANameWithConst(ssa_rep->defs[0], false).c_str())); + if (uses != 0) { + str.append(", "); + } + } + for (int i = 0; i < uses; i++) { + str.append( + StringPrintf(" %s", GetSSANameWithConst(ssa_rep->uses[i], show_singles).c_str())); + if (!show_singles && (reg_location_ != NULL) && reg_location_[i].wide) { + // For the listing, skip the high sreg. + i++; + } + if (i != (uses -1)) { + str.append(","); + } + } + switch (dalvik_format) { + case Instruction::k11n: // Add one immediate from vB + case Instruction::k21s: + case Instruction::k31i: + case Instruction::k21h: + str.append(StringPrintf(", #%d", insn.vB)); + break; + case Instruction::k51l: // Add one wide immediate + str.append(StringPrintf(", #%lld", insn.vB_wide)); + break; + case Instruction::k21c: // One register, one string/type/method index + case Instruction::k31c: + str.append(StringPrintf(", index #%d", insn.vB)); + break; + case Instruction::k22c: // Two registers, one string/type/method index + str.append(StringPrintf(", index #%d", insn.vC)); + break; + case Instruction::k22s: // Add one immediate from vC + case Instruction::k22b: + str.append(StringPrintf(", #%d", insn.vC)); + break; + default: + ; // Nothing left to print + } + } + if (nop) { + str.append("]--optimized away"); + } + int length = str.length() + 1; + ret = static_cast(arena_->NewMem(length, false, ArenaAllocator::kAllocDFInfo)); + strncpy(ret, str.c_str(), length); + return ret; +} + +/* Turn method name into a legal Linux file name */ +void MIRGraph::ReplaceSpecialChars(std::string& str) +{ + static const struct { const char before; const char after; } match[] = + {{'/','-'}, {';','#'}, {' ','#'}, {'$','+'}, + {'(','@'}, {')','@'}, {'<','='}, {'>','='}}; + for (unsigned int i = 0; i < sizeof(match)/sizeof(match[0]); i++) { + std::replace(str.begin(), str.end(), match[i].before, match[i].after); + } +} + +std::string MIRGraph::GetSSAName(int ssa_reg) +{ + return StringPrintf("v%d_%d", SRegToVReg(ssa_reg), GetSSASubscript(ssa_reg)); +} + +// Similar to GetSSAName, but if ssa name represents an immediate show that as well. +std::string MIRGraph::GetSSANameWithConst(int ssa_reg, bool singles_only) +{ + if (reg_location_ == NULL) { + // Pre-SSA - just use the standard name + return GetSSAName(ssa_reg); + } + if (IsConst(reg_location_[ssa_reg])) { + if (!singles_only && reg_location_[ssa_reg].wide) { + return StringPrintf("v%d_%d#0x%llx", SRegToVReg(ssa_reg), GetSSASubscript(ssa_reg), + ConstantValueWide(reg_location_[ssa_reg])); + } else { + return StringPrintf("v%d_%d#0x%x", SRegToVReg(ssa_reg),GetSSASubscript(ssa_reg), + ConstantValue(reg_location_[ssa_reg])); + } + } else { + return StringPrintf("v%d_%d", SRegToVReg(ssa_reg), GetSSASubscript(ssa_reg)); + } +} + +void MIRGraph::GetBlockName(BasicBlock* bb, char* name) +{ + switch (bb->block_type) { + case kEntryBlock: + snprintf(name, BLOCK_NAME_LEN, "entry_%d", bb->id); + break; + case kExitBlock: + snprintf(name, BLOCK_NAME_LEN, "exit_%d", bb->id); + break; + case kDalvikByteCode: + snprintf(name, BLOCK_NAME_LEN, "block%04x_%d", bb->start_offset, bb->id); + break; + case kExceptionHandling: + snprintf(name, BLOCK_NAME_LEN, "exception%04x_%d", bb->start_offset, + bb->id); + break; + default: + snprintf(name, BLOCK_NAME_LEN, "_%d", bb->id); + break; + } +} + +const char* MIRGraph::GetShortyFromTargetIdx(int target_idx) +{ + // FIXME: use current code unit for inline support. + const DexFile::MethodId& method_id = cu_->dex_file->GetMethodId(target_idx); + return cu_->dex_file->GetShorty(method_id.proto_idx_); +} + +/* Debug Utility - dump a compilation unit */ +void MIRGraph::DumpMIRGraph() +{ + BasicBlock* bb; + const char* block_type_names[] = { + "Entry Block", + "Code Block", + "Exit Block", + "Exception Handling", + "Catch Block" + }; + + LOG(INFO) << "Compiling " << PrettyMethod(cu_->method_idx, *cu_->dex_file); + LOG(INFO) << cu_->insns << " insns"; + LOG(INFO) << GetNumBlocks() << " blocks in total"; + GrowableArray::Iterator iterator(&block_list_); + + while (true) { + bb = iterator.Next(); + if (bb == NULL) break; + LOG(INFO) << StringPrintf("Block %d (%s) (insn %04x - %04x%s)", + bb->id, + block_type_names[bb->block_type], + bb->start_offset, + bb->last_mir_insn ? bb->last_mir_insn->offset : bb->start_offset, + bb->last_mir_insn ? "" : " empty"); + if (bb->taken) { + LOG(INFO) << " Taken branch: block " << bb->taken->id + << "(0x" << std::hex << bb->taken->start_offset << ")"; + } + if (bb->fall_through) { + LOG(INFO) << " Fallthrough : block " << bb->fall_through->id + << " (0x" << std::hex << bb->fall_through->start_offset << ")"; + } + } +} + +/* + * Build an array of location records for the incoming arguments. + * Note: one location record per word of arguments, with dummy + * high-word loc for wide arguments. Also pull up any following + * MOVE_RESULT and incorporate it into the invoke. + */ +CallInfo* MIRGraph::NewMemCallInfo(BasicBlock* bb, MIR* mir, InvokeType type, + bool is_range) +{ + CallInfo* info = static_cast(arena_->NewMem(sizeof(CallInfo), true, + ArenaAllocator::kAllocMisc)); + MIR* move_result_mir = FindMoveResult(bb, mir); + if (move_result_mir == NULL) { + info->result.location = kLocInvalid; + } else { + info->result = GetRawDest(move_result_mir); + move_result_mir->meta.original_opcode = move_result_mir->dalvikInsn.opcode; + move_result_mir->dalvikInsn.opcode = static_cast(kMirOpNop); + } + info->num_arg_words = mir->ssa_rep->num_uses; + info->args = (info->num_arg_words == 0) ? NULL : static_cast + (arena_->NewMem(sizeof(RegLocation) * info->num_arg_words, false, + ArenaAllocator::kAllocMisc)); + for (int i = 0; i < info->num_arg_words; i++) { + info->args[i] = GetRawSrc(mir, i); + } + info->opt_flags = mir->optimization_flags; + info->type = type; + info->is_range = is_range; + info->index = mir->dalvikInsn.vB; + info->offset = mir->offset; + return info; +} + +// Allocate a new basic block. +BasicBlock* MIRGraph::NewMemBB(BBType block_type, int block_id) +{ + BasicBlock* bb = static_cast(arena_->NewMem(sizeof(BasicBlock), true, + ArenaAllocator::kAllocBB)); + bb->block_type = block_type; + bb->id = block_id; + // TUNING: better estimate of the exit block predecessors? + bb->predecessors = new (arena_) + GrowableArray(arena_, (block_type == kExitBlock) ? 2048 : 2, kGrowableArrayPredecessors); + bb->successor_block_list.block_list_type = kNotUsed; + block_id_map_.Put(block_id, block_id); + return bb; +} + +} // namespace art diff --git a/src/compiler/dex/mir_graph.h b/src/compiler/dex/mir_graph.h new file mode 100644 index 0000000000000000000000000000000000000000..882a5088d7906ef1186ab2ca8884fe3d59481175 --- /dev/null +++ b/src/compiler/dex/mir_graph.h @@ -0,0 +1,675 @@ +/* + * Copyright (C) 2013 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_SRC_COMPILER_DEX_MIRGRAPH_H_ +#define ART_SRC_COMPILER_DEX_MIRGRAPH_H_ + +#include "dex_file.h" +#include "dex_instruction.h" +#include "compiler_ir.h" +#include "arena_bit_vector.h" +#include "growable_array.h" + +namespace art { + +enum DataFlowAttributePos { + kUA = 0, + kUB, + kUC, + kAWide, + kBWide, + kCWide, + kDA, + kIsMove, + kSetsConst, + kFormat35c, + kFormat3rc, + kNullCheckSrc0, // Null check of uses[0]. + kNullCheckSrc1, // Null check of uses[1]. + kNullCheckSrc2, // Null check of uses[2]. + kNullCheckOut0, // Null check out outgoing arg0. + kDstNonNull, // May assume dst is non-null. + kRetNonNull, // May assume retval is non-null. + kNullTransferSrc0, // Object copy src[0] -> dst. + kNullTransferSrcN, // Phi null check state transfer. + kRangeCheckSrc1, // Range check of uses[1]. + kRangeCheckSrc2, // Range check of uses[2]. + kRangeCheckSrc3, // Range check of uses[3]. + kFPA, + kFPB, + kFPC, + kCoreA, + kCoreB, + kCoreC, + kRefA, + kRefB, + kRefC, + kUsesMethodStar, // Implicit use of Method*. +}; + +#define DF_NOP 0 +#define DF_UA (1 << kUA) +#define DF_UB (1 << kUB) +#define DF_UC (1 << kUC) +#define DF_A_WIDE (1 << kAWide) +#define DF_B_WIDE (1 << kBWide) +#define DF_C_WIDE (1 << kCWide) +#define DF_DA (1 << kDA) +#define DF_IS_MOVE (1 << kIsMove) +#define DF_SETS_CONST (1 << kSetsConst) +#define DF_FORMAT_35C (1 << kFormat35c) +#define DF_FORMAT_3RC (1 << kFormat3rc) +#define DF_NULL_CHK_0 (1 << kNullCheckSrc0) +#define DF_NULL_CHK_1 (1 << kNullCheckSrc1) +#define DF_NULL_CHK_2 (1 << kNullCheckSrc2) +#define DF_NULL_CHK_OUT0 (1 << kNullCheckOut0) +#define DF_NON_NULL_DST (1 << kDstNonNull) +#define DF_NON_NULL_RET (1 << kRetNonNull) +#define DF_NULL_TRANSFER_0 (1 << kNullTransferSrc0) +#define DF_NULL_TRANSFER_N (1 << kNullTransferSrcN) +#define DF_RANGE_CHK_1 (1 << kRangeCheckSrc1) +#define DF_RANGE_CHK_2 (1 << kRangeCheckSrc2) +#define DF_RANGE_CHK_3 (1 << kRangeCheckSrc3) +#define DF_FP_A (1 << kFPA) +#define DF_FP_B (1 << kFPB) +#define DF_FP_C (1 << kFPC) +#define DF_CORE_A (1 << kCoreA) +#define DF_CORE_B (1 << kCoreB) +#define DF_CORE_C (1 << kCoreC) +#define DF_REF_A (1 << kRefA) +#define DF_REF_B (1 << kRefB) +#define DF_REF_C (1 << kRefC) +#define DF_UMS (1 << kUsesMethodStar) + +#define DF_HAS_USES (DF_UA | DF_UB | DF_UC) + +#define DF_HAS_DEFS (DF_DA) + +#define DF_HAS_NULL_CHKS (DF_NULL_CHK_0 | \ + DF_NULL_CHK_1 | \ + DF_NULL_CHK_2 | \ + DF_NULL_CHK_OUT0) + +#define DF_HAS_RANGE_CHKS (DF_RANGE_CHK_1 | \ + DF_RANGE_CHK_2 | \ + DF_RANGE_CHK_3) + +#define DF_HAS_NR_CHKS (DF_HAS_NULL_CHKS | \ + DF_HAS_RANGE_CHKS) + +#define DF_A_IS_REG (DF_UA | DF_DA) +#define DF_B_IS_REG (DF_UB) +#define DF_C_IS_REG (DF_UC) +#define DF_IS_GETTER_OR_SETTER (DF_IS_GETTER | DF_IS_SETTER) +#define DF_USES_FP (DF_FP_A | DF_FP_B | DF_FP_C) + +enum OatMethodAttributes { + kIsLeaf, // Method is leaf. + kHasLoop, // Method contains simple loop. +}; + +#define METHOD_IS_LEAF (1 << kIsLeaf) +#define METHOD_HAS_LOOP (1 << kHasLoop) + +// Minimum field size to contain Dalvik v_reg number. +#define VREG_NUM_WIDTH 16 + +#define INVALID_SREG (-1) +#define INVALID_VREG (0xFFFFU) +#define INVALID_REG (0xFF) +#define INVALID_OFFSET (0xDEADF00FU) + +/* SSA encodings for special registers */ +#define SSA_METHOD_BASEREG (-2) +/* First compiler temp basereg, grows smaller */ +#define SSA_CTEMP_BASEREG (SSA_METHOD_BASEREG - 1) + +#define MIR_IGNORE_NULL_CHECK (1 << kMIRIgnoreNullCheck) +#define MIR_NULL_CHECK_ONLY (1 << kMIRNullCheckOnly) +#define MIR_IGNORE_RANGE_CHECK (1 << kMIRIgnoreRangeCheck) +#define MIR_RANGE_CHECK_ONLY (1 << kMIRRangeCheckOnly) +#define MIR_INLINED (1 << kMIRInlined) +#define MIR_INLINED_PRED (1 << kMIRInlinedPred) +#define MIR_CALLEE (1 << kMIRCallee) +#define MIR_IGNORE_SUSPEND_CHECK (1 << kMIRIgnoreSuspendCheck) +#define MIR_DUP (1 << kMIRDup) + +#define BLOCK_NAME_LEN 80 + +/* + * In general, vreg/sreg describe Dalvik registers that originated with dx. However, + * it is useful to have compiler-generated temporary registers and have them treated + * in the same manner as dx-generated virtual registers. This struct records the SSA + * name of compiler-introduced temporaries. + */ +struct CompilerTemp { + int s_reg; +}; + +// When debug option enabled, records effectiveness of null and range check elimination. +struct Checkstats { + int null_checks; + int null_checks_eliminated; + int range_checks; + int range_checks_eliminated; +}; + +// Dataflow attributes of a basic block. +struct BasicBlockDataFlow { + ArenaBitVector* use_v; + ArenaBitVector* def_v; + ArenaBitVector* live_in_v; + ArenaBitVector* phi_v; + int* vreg_to_ssa_map; + ArenaBitVector* ending_null_check_v; +}; + +/* + * Normalized use/def for a MIR operation using SSA names rather than vregs. Note that + * uses/defs retain the Dalvik convention that long operations operate on a pair of 32-bit + * vregs. For example, "ADD_LONG v0, v2, v3" would have 2 defs (v0/v1) and 4 uses (v2/v3, v4/v5). + * Following SSA renaming, this is the primary struct used by code generators to locate + * operand and result registers. This is a somewhat confusing and unhelpful convention that + * we may want to revisit in the future. + */ +struct SSARepresentation { + int num_uses; + int* uses; + bool* fp_use; + int num_defs; + int* defs; + bool* fp_def; +}; + +/* + * The Midlevel Intermediate Representation node, which may be largely considered a + * wrapper around a Dalvik byte code. + */ +struct MIR { + DecodedInstruction dalvikInsn; + unsigned int width; + unsigned int offset; + int m_unit_index; // From which method was this MIR included + MIR* prev; + MIR* next; + SSARepresentation* ssa_rep; + int optimization_flags; + union { + // Establish link between two halves of throwing instructions. + MIR* throw_insn; + // Saved opcode for NOP'd MIRs + Instruction::Code original_opcode; + } meta; +}; + +struct SuccessorBlockInfo; + +struct BasicBlock { + int id; + int dfs_id; + bool visited; + bool hidden; + bool catch_entry; + bool explicit_throw; + bool conditional_branch; + bool terminated_by_return; // Block ends with a Dalvik return opcode. + bool dominates_return; // Is a member of return extended basic block. + uint16_t start_offset; + uint16_t nesting_depth; + BBType block_type; + MIR* first_mir_insn; + MIR* last_mir_insn; + BasicBlock* fall_through; + BasicBlock* taken; + BasicBlock* i_dom; // Immediate dominator. + BasicBlockDataFlow* data_flow_info; + GrowableArray* predecessors; + ArenaBitVector* dominators; + ArenaBitVector* i_dominated; // Set nodes being immediately dominated. + ArenaBitVector* dom_frontier; // Dominance frontier. + struct { // For one-to-many successors like. + BlockListType block_list_type; // switch and exception handling. + GrowableArray* blocks; + } successor_block_list; +}; + +/* + * The "blocks" field in "successor_block_list" points to an array of elements with the type + * "SuccessorBlockInfo". For catch blocks, key is type index for the exception. For swtich + * blocks, key is the case value. + */ +// TODO: make class with placement new. +struct SuccessorBlockInfo { + BasicBlock* block; + int key; +}; + +/* + * Whereas a SSA name describes a definition of a Dalvik vreg, the RegLocation describes + * the type of an SSA name (and, can also be used by code generators to record where the + * value is located (i.e. - physical register, frame, spill, etc.). For each SSA name (SReg) + * there is a RegLocation. + * FIXME: The orig_sreg field was added as a workaround for llvm bitcode generation. With + * the latest restructuring, we should be able to remove it and rely on s_reg_low throughout. + */ +struct RegLocation { + RegLocationType location:3; + unsigned wide:1; + unsigned defined:1; // Do we know the type? + unsigned is_const:1; // Constant, value in mir_graph->constant_values[]. + unsigned fp:1; // Floating point? + unsigned core:1; // Non-floating point? + unsigned ref:1; // Something GC cares about. + unsigned high_word:1; // High word of pair? + unsigned home:1; // Does this represent the home location? + uint8_t low_reg; // First physical register. + uint8_t high_reg; // 2nd physical register (if wide). + int32_t s_reg_low; // SSA name for low Dalvik word. + int32_t orig_sreg; // TODO: remove after Bitcode gen complete + // and consolodate usage w/ s_reg_low. +}; + +/* + * Collection of information describing an invoke, and the destination of + * the subsequent MOVE_RESULT (if applicable). Collected as a unit to enable + * more efficient invoke code generation. + */ +struct CallInfo { + int num_arg_words; // Note: word count, not arg count. + RegLocation* args; // One for each word of arguments. + RegLocation result; // Eventual target of MOVE_RESULT. + int opt_flags; + InvokeType type; + uint32_t dex_idx; + uint32_t index; // Method idx for invokes, type idx for FilledNewArray. + uintptr_t direct_code; + uintptr_t direct_method; + RegLocation target; // Target of following move_result. + bool skip_this; + bool is_range; + int offset; // Dalvik offset. +}; + + +const RegLocation bad_loc = {kLocDalvikFrame, 0, 0, 0, 0, 0, 0, 0, 0, + INVALID_REG, INVALID_REG, INVALID_SREG, INVALID_SREG}; + +class MIRGraph { + public: + MIRGraph(CompilationUnit* cu, ArenaAllocator* arena); + ~MIRGraph(); + + /* + * Parse dex method and add MIR at current insert point. Returns id (which is + * actually the index of the method in the m_units_ array). + */ + void InlineMethod(const DexFile::CodeItem* code_item, uint32_t access_flags, + InvokeType invoke_type, uint32_t class_def_idx, + uint32_t method_idx, jobject class_loader, const DexFile& dex_file); + + /* Find existing block */ + BasicBlock* FindBlock(unsigned int code_offset) { + return FindBlock(code_offset, false, false, NULL); + } + + const uint16_t* GetCurrentInsns() const { + return current_code_item_->insns_; + } + + const uint16_t* GetInsns(int m_unit_index) const { + return m_units_[m_unit_index]->GetCodeItem()->insns_; + } + + int GetNumBlocks() const { + return num_blocks_; + } + + ArenaBitVector* GetTryBlockAddr() const { + return try_block_addr_; + } + + BasicBlock* GetEntryBlock() const { + return entry_block_; + } + + BasicBlock* GetExitBlock() const { + return exit_block_; + } + + BasicBlock* GetBasicBlock(int block_id) const { + return block_list_.Get(block_id); + } + + size_t GetBasicBlockListCount() const { + return block_list_.Size(); + } + + GrowableArray* GetBlockList() { + return &block_list_; + } + + GrowableArray* GetDfsOrder() { + return dfs_order_; + } + + GrowableArray* GetDfsPostOrder() { + return dfs_post_order_; + } + + GrowableArray* GetDomPostOrder() { + return dom_post_order_traversal_; + } + + int GetDefCount() const { + return def_count_; + } + + ArenaAllocator* GetArena() { + return arena_; + } + + void EnableOpcodeCounting() { + opcode_count_ = static_cast(arena_->NewMem(kNumPackedOpcodes * sizeof(int), true, + ArenaAllocator::kAllocMisc)); + } + + void ShowOpcodeStats(); + + DexCompilationUnit* GetCurrentDexCompilationUnit() const { + return m_units_[current_method_]; + } + + void DumpCFG(const char* dir_prefix, bool all_blocks); + + void BuildRegLocations(); + + void DumpRegLocTable(RegLocation* table, int count); + + void BasicBlockOptimization(); + + bool IsConst(int32_t s_reg) const { + return is_constant_v_->IsBitSet(s_reg); + } + + bool IsConst(RegLocation loc) const { + return (IsConst(loc.orig_sreg)); + } + + int32_t ConstantValue(RegLocation loc) const { + DCHECK(IsConst(loc)); + return constant_values_[loc.orig_sreg]; + } + + int32_t ConstantValue(int32_t s_reg) const { + DCHECK(IsConst(s_reg)); + return constant_values_[s_reg]; + } + + int64_t ConstantValueWide(RegLocation loc) const { + DCHECK(IsConst(loc)); + return (static_cast(constant_values_[loc.orig_sreg + 1]) << 32) | + Low32Bits(static_cast(constant_values_[loc.orig_sreg])); + } + + bool IsConstantNullRef(RegLocation loc) const { + return loc.ref && loc.is_const && (ConstantValue(loc) == 0); + } + + int GetNumSSARegs() const { + return num_ssa_regs_; + } + + void SetNumSSARegs(int new_num) { + num_ssa_regs_ = new_num; + } + + unsigned int GetNumReachableBlocks() const { + return num_reachable_blocks_; + } + + int GetUseCount(int vreg) const { + return use_counts_.Get(vreg); + } + + int GetRawUseCount(int vreg) const { + return raw_use_counts_.Get(vreg); + } + + int GetSSASubscript(int ssa_reg) const { + return ssa_subscripts_->Get(ssa_reg); + } + + const char* GetSSAString(int ssa_reg) const { + return ssa_strings_->Get(ssa_reg); + } + + RegLocation GetRawSrc(MIR* mir, int num) + { + DCHECK(num < mir->ssa_rep->num_uses); + RegLocation res = reg_location_[mir->ssa_rep->uses[num]]; + return res; + } + + RegLocation GetRawDest(MIR* mir) + { + DCHECK_GT(mir->ssa_rep->num_defs, 0); + RegLocation res = reg_location_[mir->ssa_rep->defs[0]]; + return res; + } + + RegLocation GetDest(MIR* mir) + { + RegLocation res = GetRawDest(mir); + DCHECK(!res.wide); + return res; + } + + RegLocation GetSrc(MIR* mir, int num) + { + RegLocation res = GetRawSrc(mir, num); + DCHECK(!res.wide); + return res; + } + + RegLocation GetDestWide(MIR* mir) + { + RegLocation res = GetRawDest(mir); + DCHECK(res.wide); + return res; + } + + RegLocation GetSrcWide(MIR* mir, int low) + { + RegLocation res = GetRawSrc(mir, low); + DCHECK(res.wide); + return res; + } + + RegLocation GetBadLoc() { + return bad_loc; + } + + int GetMethodSReg() { + return method_sreg_; + } + + bool MethodIsLeaf() { + return attributes_ & METHOD_IS_LEAF; + } + + RegLocation GetRegLocation(int index) { + DCHECK((index >= 0) && (index > num_ssa_regs_)); + return reg_location_[index]; + } + + RegLocation GetMethodLoc() { + return reg_location_[method_sreg_]; + } + + void BasicBlockCombine(); + void CodeLayout(); + void DumpCheckStats(); + void PropagateConstants(); + MIR* FindMoveResult(BasicBlock* bb, MIR* mir); + int SRegToVReg(int ssa_reg) const; + void VerifyDataflow(); + void MethodUseCount(); + void SSATransformation(); + void CheckForDominanceFrontier(BasicBlock* dom_bb, const BasicBlock* succ_bb); + void NullCheckElimination(); + bool SetFp(int index, bool is_fp); + bool SetCore(int index, bool is_core); + bool SetRef(int index, bool is_ref); + bool SetWide(int index, bool is_wide); + bool SetHigh(int index, bool is_high); + void AppendMIR(BasicBlock* bb, MIR* mir); + void PrependMIR(BasicBlock* bb, MIR* mir); + void InsertMIRAfter(BasicBlock* bb, MIR* current_mir, MIR* new_mir); + char* GetDalvikDisassembly(const MIR* mir); + void ReplaceSpecialChars(std::string& str); + std::string GetSSAName(int ssa_reg); + std::string GetSSANameWithConst(int ssa_reg, bool singles_only); + void GetBlockName(BasicBlock* bb, char* name); + const char* GetShortyFromTargetIdx(int); + void DumpMIRGraph(); + CallInfo* NewMemCallInfo(BasicBlock* bb, MIR* mir, InvokeType type, bool is_range); + BasicBlock* NewMemBB(BBType block_type, int block_id); + + /* + * IsDebugBuild sanity check: keep track of the Dex PCs for catch entries so that later on + * we can verify that all catch entries have native PC entries. + */ + std::set catches_; + + // TODO: make these private. + RegLocation* reg_location_; // Map SSA names to location. + GrowableArray compiler_temps_; + SafeMap block_id_map_; // Block collapse lookup cache. + + static const int oat_data_flow_attributes_[kMirOpLast]; + static const char* extended_mir_op_names_[kMirOpLast - kMirOpFirst]; + + private: + + int FindCommonParent(int block1, int block2); + void ComputeSuccLineIn(ArenaBitVector* dest, const ArenaBitVector* src1, + const ArenaBitVector* src2); + void HandleLiveInUse(ArenaBitVector* use_v, ArenaBitVector* def_v, + ArenaBitVector* live_in_v, int dalvik_reg_id); + void HandleDef(ArenaBitVector* def_v, int dalvik_reg_id); + void CompilerInitializeSSAConversion(); + bool DoSSAConversion(BasicBlock* bb); + bool InvokeUsesMethodStar(MIR* mir); + int ParseInsn(const uint16_t* code_ptr, DecodedInstruction* decoded_instruction); + bool ContentIsInsn(const uint16_t* code_ptr); + BasicBlock* SplitBlock(unsigned int code_offset, BasicBlock* orig_block, + BasicBlock** immed_pred_block_p); + BasicBlock* FindBlock(unsigned int code_offset, bool split, bool create, + BasicBlock** immed_pred_block_p); + void ProcessTryCatchBlocks(); + BasicBlock* ProcessCanBranch(BasicBlock* cur_block, MIR* insn, int cur_offset, int width, + int flags, const uint16_t* code_ptr, const uint16_t* code_end); + void ProcessCanSwitch(BasicBlock* cur_block, MIR* insn, int cur_offset, int width, int flags); + BasicBlock* ProcessCanThrow(BasicBlock* cur_block, MIR* insn, int cur_offset, int width, + int flags, ArenaBitVector* try_block_addr, const uint16_t* code_ptr, + const uint16_t* code_end); + int AddNewSReg(int v_reg); + void HandleSSAUse(int* uses, int dalvik_reg, int reg_index); + void HandleSSADef(int* defs, int dalvik_reg, int reg_index); + void DataFlowSSAFormat35C(MIR* mir); + void DataFlowSSAFormat3RC(MIR* mir); + bool FindLocalLiveIn(BasicBlock* bb); + void ClearAllVisitedFlags(); + bool CountUses(struct BasicBlock* bb); + bool InferTypeAndSize(BasicBlock* bb); + bool VerifyPredInfo(BasicBlock* bb); + BasicBlock* NeedsVisit(BasicBlock* bb); + BasicBlock* NextUnvisitedSuccessor(BasicBlock* bb); + void MarkPreOrder(BasicBlock* bb); + void RecordDFSOrders(BasicBlock* bb); + void ComputeDFSOrders(); + void ComputeDefBlockMatrix(); + void ComputeDomPostOrderTraversal(BasicBlock* bb); + void ComputeDominators(); + void InsertPhiNodes(); + void DoDFSPreOrderSSARename(BasicBlock* block); + void SetConstant(int32_t ssa_reg, int value); + void SetConstantWide(int ssa_reg, int64_t value); + int GetSSAUseCount(int s_reg); + bool BasicBlockOpt(BasicBlock* bb); + bool EliminateNullChecks(BasicBlock* bb); + void NullCheckEliminationInit(BasicBlock* bb); + bool BuildExtendedBBList(struct BasicBlock* bb); + bool FillDefBlockMatrix(BasicBlock* bb); + void InitializeDominationInfo(BasicBlock* bb); + bool ComputeblockIDom(BasicBlock* bb); + bool ComputeBlockDominators(BasicBlock* bb); + bool SetDominators(BasicBlock* bb); + bool ComputeBlockLiveIns(BasicBlock* bb); + bool InsertPhiNodeOperands(BasicBlock* bb); + bool ComputeDominanceFrontier(BasicBlock* bb); + void DoConstantPropogation(BasicBlock* bb); + void CountChecks(BasicBlock* bb); + bool CombineBlocks(BasicBlock* bb); + + CompilationUnit* const cu_; + GrowableArray* ssa_base_vregs_; + GrowableArray* ssa_subscripts_; + GrowableArray* ssa_strings_; + // Map original Dalvik virtual reg i to the current SSA name. + int* vreg_to_ssa_map_; // length == method->registers_size + int* ssa_last_defs_; // length == method->registers_size + ArenaBitVector* is_constant_v_; // length == num_ssa_reg + int* constant_values_; // length == num_ssa_reg + // Use counts of ssa names. + GrowableArray use_counts_; // Weighted by nesting depth + GrowableArray raw_use_counts_; // Not weighted + unsigned int num_reachable_blocks_; + GrowableArray* dfs_order_; + GrowableArray* dfs_post_order_; + GrowableArray* dom_post_order_traversal_; + int* i_dom_list_; + ArenaBitVector** def_block_matrix_; // num_dalvik_register x num_blocks. + ArenaBitVector* temp_block_v_; + ArenaBitVector* temp_dalvik_register_v_; + ArenaBitVector* temp_ssa_register_v_; // num_ssa_regs. + static const int kInvalidEntry = -1; + GrowableArray block_list_; + ArenaBitVector* try_block_addr_; + BasicBlock* entry_block_; + BasicBlock* exit_block_; + BasicBlock* cur_block_; + int num_blocks_; + const DexFile::CodeItem* current_code_item_; + SafeMap block_map_; // FindBlock lookup cache. + std::vector m_units_; // List of methods included in this graph + typedef std::pair MIRLocation; // Insert point, (m_unit_ index, offset) + std::vector method_stack_; // Include stack + int current_method_; + int current_offset_; + int def_count_; // Used to estimate size of ssa name storage. + int* opcode_count_; // Dex opcode coverage stats. + int num_ssa_regs_; // Number of names following SSA transformation. + std::vector extended_basic_blocks_; // Heads of block "traces". + int method_sreg_; + unsigned int attributes_; + Checkstats* checkstats_; + ArenaAllocator* arena_; +}; + +} // namespace art + +#endif // ART_SRC_COMPILER_DEX_MIRGRAPH_H_ diff --git a/src/compiler/dex/mir_optimization.cc b/src/compiler/dex/mir_optimization.cc new file mode 100644 index 0000000000000000000000000000000000000000..534550112a1eb4cc4b330bcea7a91bd5fa11b1b1 --- /dev/null +++ b/src/compiler/dex/mir_optimization.cc @@ -0,0 +1,865 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compiler_internals.h" +#include "local_value_numbering.h" +#include "dataflow_iterator.h" + +namespace art { + +static unsigned int Predecessors(BasicBlock* bb) +{ + return bb->predecessors->Size(); +} + +/* Setup a constant value for opcodes thare have the DF_SETS_CONST attribute */ +void MIRGraph::SetConstant(int32_t ssa_reg, int value) +{ + is_constant_v_->SetBit(ssa_reg); + constant_values_[ssa_reg] = value; +} + +void MIRGraph::SetConstantWide(int ssa_reg, int64_t value) +{ + is_constant_v_->SetBit(ssa_reg); + constant_values_[ssa_reg] = Low32Bits(value); + constant_values_[ssa_reg + 1] = High32Bits(value); +} + +void MIRGraph::DoConstantPropogation(BasicBlock* bb) +{ + MIR* mir; + + for (mir = bb->first_mir_insn; mir != NULL; mir = mir->next) { + int df_attributes = oat_data_flow_attributes_[mir->dalvikInsn.opcode]; + + DecodedInstruction *d_insn = &mir->dalvikInsn; + + if (!(df_attributes & DF_HAS_DEFS)) continue; + + /* Handle instructions that set up constants directly */ + if (df_attributes & DF_SETS_CONST) { + if (df_attributes & DF_DA) { + int32_t vB = static_cast(d_insn->vB); + switch (d_insn->opcode) { + case Instruction::CONST_4: + case Instruction::CONST_16: + case Instruction::CONST: + SetConstant(mir->ssa_rep->defs[0], vB); + break; + case Instruction::CONST_HIGH16: + SetConstant(mir->ssa_rep->defs[0], vB << 16); + break; + case Instruction::CONST_WIDE_16: + case Instruction::CONST_WIDE_32: + SetConstantWide(mir->ssa_rep->defs[0], static_cast(vB)); + break; + case Instruction::CONST_WIDE: + SetConstantWide(mir->ssa_rep->defs[0],d_insn->vB_wide); + break; + case Instruction::CONST_WIDE_HIGH16: + SetConstantWide(mir->ssa_rep->defs[0], static_cast(vB) << 48); + break; + default: + break; + } + } + /* Handle instructions that set up constants directly */ + } else if (df_attributes & DF_IS_MOVE) { + int i; + + for (i = 0; i < mir->ssa_rep->num_uses; i++) { + if (!is_constant_v_->IsBitSet(mir->ssa_rep->uses[i])) break; + } + /* Move a register holding a constant to another register */ + if (i == mir->ssa_rep->num_uses) { + SetConstant(mir->ssa_rep->defs[0], constant_values_[mir->ssa_rep->uses[0]]); + if (df_attributes & DF_A_WIDE) { + SetConstant(mir->ssa_rep->defs[1], constant_values_[mir->ssa_rep->uses[1]]); + } + } + } + } + /* TODO: implement code to handle arithmetic operations */ +} + +void MIRGraph::PropagateConstants() +{ + is_constant_v_ = new (arena_) ArenaBitVector(arena_, GetNumSSARegs(), false); + constant_values_ = static_cast(arena_->NewMem(sizeof(int) * GetNumSSARegs(), true, + ArenaAllocator::kAllocDFInfo)); + AllNodesIterator iter(this, false /* not iterative */); + for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { + DoConstantPropogation(bb); + } +} + +/* Advance to next strictly dominated MIR node in an extended basic block */ +static MIR* AdvanceMIR(BasicBlock** p_bb, MIR* mir) +{ + BasicBlock* bb = *p_bb; + if (mir != NULL) { + mir = mir->next; + if (mir == NULL) { + bb = bb->fall_through; + if ((bb == NULL) || Predecessors(bb) != 1) { + mir = NULL; + } else { + *p_bb = bb; + mir = bb->first_mir_insn; + } + } + } + return mir; +} + +/* + * To be used at an invoke mir. If the logically next mir node represents + * a move-result, return it. Else, return NULL. If a move-result exists, + * it is required to immediately follow the invoke with no intervening + * opcodes or incoming arcs. However, if the result of the invoke is not + * used, a move-result may not be present. + */ +MIR* MIRGraph::FindMoveResult(BasicBlock* bb, MIR* mir) +{ + BasicBlock* tbb = bb; + mir = AdvanceMIR(&tbb, mir); + while (mir != NULL) { + int opcode = mir->dalvikInsn.opcode; + if ((mir->dalvikInsn.opcode == Instruction::MOVE_RESULT) || + (mir->dalvikInsn.opcode == Instruction::MOVE_RESULT_OBJECT) || + (mir->dalvikInsn.opcode == Instruction::MOVE_RESULT_WIDE)) { + break; + } + // Keep going if pseudo op, otherwise terminate + if (opcode < kNumPackedOpcodes) { + mir = NULL; + } else { + mir = AdvanceMIR(&tbb, mir); + } + } + return mir; +} + +static BasicBlock* NextDominatedBlock(BasicBlock* bb) +{ + if (bb->block_type == kDead) { + return NULL; + } + DCHECK((bb->block_type == kEntryBlock) || (bb->block_type == kDalvikByteCode) + || (bb->block_type == kExitBlock)); + bb = bb->fall_through; + if (bb == NULL || (Predecessors(bb) != 1)) { + return NULL; + } + DCHECK((bb->block_type == kDalvikByteCode) || (bb->block_type == kExitBlock)); + return bb; +} + +static MIR* FindPhi(BasicBlock* bb, int ssa_name) +{ + for (MIR* mir = bb->first_mir_insn; mir != NULL; mir = mir->next) { + if (static_cast(mir->dalvikInsn.opcode) == kMirOpPhi) { + for (int i = 0; i < mir->ssa_rep->num_uses; i++) { + if (mir->ssa_rep->uses[i] == ssa_name) { + return mir; + } + } + } + } + return NULL; +} + +static SelectInstructionKind SelectKind(MIR* mir) +{ + switch (mir->dalvikInsn.opcode) { + case Instruction::MOVE: + case Instruction::MOVE_OBJECT: + case Instruction::MOVE_16: + case Instruction::MOVE_OBJECT_16: + case Instruction::MOVE_FROM16: + case Instruction::MOVE_OBJECT_FROM16: + return kSelectMove; + case Instruction::CONST: + case Instruction::CONST_4: + case Instruction::CONST_16: + return kSelectConst; + case Instruction::GOTO: + case Instruction::GOTO_16: + case Instruction::GOTO_32: + return kSelectGoto; + default:; + } + return kSelectNone; +} + +int MIRGraph::GetSSAUseCount(int s_reg) +{ + return raw_use_counts_.Get(s_reg); +} + + +/* Do some MIR-level extended basic block optimizations */ +bool MIRGraph::BasicBlockOpt(BasicBlock* bb) +{ + if (bb->block_type == kDead) { + return true; + } + int num_temps = 0; + LocalValueNumbering local_valnum(cu_); + while (bb != NULL) { + for (MIR* mir = bb->first_mir_insn; mir != NULL; mir = mir->next) { + // TUNING: use the returned value number for CSE. + local_valnum.GetValueNumber(mir); + // Look for interesting opcodes, skip otherwise + Instruction::Code opcode = mir->dalvikInsn.opcode; + switch (opcode) { + case Instruction::CMPL_FLOAT: + case Instruction::CMPL_DOUBLE: + case Instruction::CMPG_FLOAT: + case Instruction::CMPG_DOUBLE: + case Instruction::CMP_LONG: + if ((cu_->disable_opt & (1 << kBranchFusing)) != 0) { + // Bitcode doesn't allow this optimization. + break; + } + if (mir->next != NULL) { + MIR* mir_next = mir->next; + Instruction::Code br_opcode = mir_next->dalvikInsn.opcode; + ConditionCode ccode = kCondNv; + switch(br_opcode) { + case Instruction::IF_EQZ: + ccode = kCondEq; + break; + case Instruction::IF_NEZ: + ccode = kCondNe; + break; + case Instruction::IF_LTZ: + ccode = kCondLt; + break; + case Instruction::IF_GEZ: + ccode = kCondGe; + break; + case Instruction::IF_GTZ: + ccode = kCondGt; + break; + case Instruction::IF_LEZ: + ccode = kCondLe; + break; + default: + break; + } + // Make sure result of cmp is used by next insn and nowhere else + if ((ccode != kCondNv) && + (mir->ssa_rep->defs[0] == mir_next->ssa_rep->uses[0]) && + (GetSSAUseCount(mir->ssa_rep->defs[0]) == 1)) { + mir_next->dalvikInsn.arg[0] = ccode; + switch(opcode) { + case Instruction::CMPL_FLOAT: + mir_next->dalvikInsn.opcode = + static_cast(kMirOpFusedCmplFloat); + break; + case Instruction::CMPL_DOUBLE: + mir_next->dalvikInsn.opcode = + static_cast(kMirOpFusedCmplDouble); + break; + case Instruction::CMPG_FLOAT: + mir_next->dalvikInsn.opcode = + static_cast(kMirOpFusedCmpgFloat); + break; + case Instruction::CMPG_DOUBLE: + mir_next->dalvikInsn.opcode = + static_cast(kMirOpFusedCmpgDouble); + break; + case Instruction::CMP_LONG: + mir_next->dalvikInsn.opcode = + static_cast(kMirOpFusedCmpLong); + break; + default: LOG(ERROR) << "Unexpected opcode: " << opcode; + } + mir->dalvikInsn.opcode = static_cast(kMirOpNop); + mir_next->ssa_rep->num_uses = mir->ssa_rep->num_uses; + mir_next->ssa_rep->uses = mir->ssa_rep->uses; + mir_next->ssa_rep->fp_use = mir->ssa_rep->fp_use; + mir_next->ssa_rep->num_defs = 0; + mir->ssa_rep->num_uses = 0; + mir->ssa_rep->num_defs = 0; + } + } + break; + case Instruction::GOTO: + case Instruction::GOTO_16: + case Instruction::GOTO_32: + case Instruction::IF_EQ: + case Instruction::IF_NE: + case Instruction::IF_LT: + case Instruction::IF_GE: + case Instruction::IF_GT: + case Instruction::IF_LE: + case Instruction::IF_EQZ: + case Instruction::IF_NEZ: + case Instruction::IF_LTZ: + case Instruction::IF_GEZ: + case Instruction::IF_GTZ: + case Instruction::IF_LEZ: + if (bb->taken->dominates_return) { + mir->optimization_flags |= MIR_IGNORE_SUSPEND_CHECK; + if (cu_->verbose) { + LOG(INFO) << "Suppressed suspend check on branch to return at 0x" << std::hex << mir->offset; + } + } + break; + default: + break; + } + // Is this the select pattern? + // TODO: flesh out support for Mips and X86. NOTE: llvm's select op doesn't quite work here. + // TUNING: expand to support IF_xx compare & branches + if (!(cu_->compiler_backend == kPortable) && (cu_->instruction_set == kThumb2) && + ((mir->dalvikInsn.opcode == Instruction::IF_EQZ) || + (mir->dalvikInsn.opcode == Instruction::IF_NEZ))) { + BasicBlock* ft = bb->fall_through; + DCHECK(ft != NULL); + BasicBlock* ft_ft = ft->fall_through; + BasicBlock* ft_tk = ft->taken; + + BasicBlock* tk = bb->taken; + DCHECK(tk != NULL); + BasicBlock* tk_ft = tk->fall_through; + BasicBlock* tk_tk = tk->taken; + + /* + * In the select pattern, the taken edge goes to a block that unconditionally + * transfers to the rejoin block and the fall_though edge goes to a block that + * unconditionally falls through to the rejoin block. + */ + if ((tk_ft == NULL) && (ft_tk == NULL) && (tk_tk == ft_ft) && + (Predecessors(tk) == 1) && (Predecessors(ft) == 1)) { + /* + * Okay - we have the basic diamond shape. At the very least, we can eliminate the + * suspend check on the taken-taken branch back to the join point. + */ + if (SelectKind(tk->last_mir_insn) == kSelectGoto) { + tk->last_mir_insn->optimization_flags |= (MIR_IGNORE_SUSPEND_CHECK); + } + // Are the block bodies something we can handle? + if ((ft->first_mir_insn == ft->last_mir_insn) && + (tk->first_mir_insn != tk->last_mir_insn) && + (tk->first_mir_insn->next == tk->last_mir_insn) && + ((SelectKind(ft->first_mir_insn) == kSelectMove) || + (SelectKind(ft->first_mir_insn) == kSelectConst)) && + (SelectKind(ft->first_mir_insn) == SelectKind(tk->first_mir_insn)) && + (SelectKind(tk->last_mir_insn) == kSelectGoto)) { + // Almost there. Are the instructions targeting the same vreg? + MIR* if_true = tk->first_mir_insn; + MIR* if_false = ft->first_mir_insn; + // It's possible that the target of the select isn't used - skip those (rare) cases. + MIR* phi = FindPhi(tk_tk, if_true->ssa_rep->defs[0]); + if ((phi != NULL) && (if_true->dalvikInsn.vA == if_false->dalvikInsn.vA)) { + /* + * We'll convert the IF_EQZ/IF_NEZ to a SELECT. We need to find the + * Phi node in the merge block and delete it (while using the SSA name + * of the merge as the target of the SELECT. Delete both taken and + * fallthrough blocks, and set fallthrough to merge block. + * NOTE: not updating other dataflow info (no longer used at this point). + * If this changes, need to update i_dom, etc. here (and in CombineBlocks). + */ + if (opcode == Instruction::IF_NEZ) { + // Normalize. + MIR* tmp_mir = if_true; + if_true = if_false; + if_false = tmp_mir; + } + mir->dalvikInsn.opcode = static_cast(kMirOpSelect); + bool const_form = (SelectKind(if_true) == kSelectConst); + if ((SelectKind(if_true) == kSelectMove)) { + if (IsConst(if_true->ssa_rep->uses[0]) && + IsConst(if_false->ssa_rep->uses[0])) { + const_form = true; + if_true->dalvikInsn.vB = ConstantValue(if_true->ssa_rep->uses[0]); + if_false->dalvikInsn.vB = ConstantValue(if_false->ssa_rep->uses[0]); + } + } + if (const_form) { + // "true" set val in vB + mir->dalvikInsn.vB = if_true->dalvikInsn.vB; + // "false" set val in vC + mir->dalvikInsn.vC = if_false->dalvikInsn.vB; + } else { + DCHECK_EQ(SelectKind(if_true), kSelectMove); + DCHECK_EQ(SelectKind(if_false), kSelectMove); + int* src_ssa = + static_cast(arena_->NewMem(sizeof(int) * 3, false, + ArenaAllocator::kAllocDFInfo)); + src_ssa[0] = mir->ssa_rep->uses[0]; + src_ssa[1] = if_true->ssa_rep->uses[0]; + src_ssa[2] = if_false->ssa_rep->uses[0]; + mir->ssa_rep->uses = src_ssa; + mir->ssa_rep->num_uses = 3; + } + mir->ssa_rep->num_defs = 1; + mir->ssa_rep->defs = + static_cast(arena_->NewMem(sizeof(int) * 1, false, + ArenaAllocator::kAllocDFInfo)); + mir->ssa_rep->fp_def = + static_cast(arena_->NewMem(sizeof(bool) * 1, false, + ArenaAllocator::kAllocDFInfo)); + mir->ssa_rep->fp_def[0] = if_true->ssa_rep->fp_def[0]; + /* + * There is usually a Phi node in the join block for our two cases. If the + * Phi node only contains our two cases as input, we will use the result + * SSA name of the Phi node as our select result and delete the Phi. If + * the Phi node has more than two operands, we will arbitrarily use the SSA + * name of the "true" path, delete the SSA name of the "false" path from the + * Phi node (and fix up the incoming arc list). + */ + if (phi->ssa_rep->num_uses == 2) { + mir->ssa_rep->defs[0] = phi->ssa_rep->defs[0]; + phi->dalvikInsn.opcode = static_cast(kMirOpNop); + } else { + int dead_def = if_false->ssa_rep->defs[0]; + int live_def = if_true->ssa_rep->defs[0]; + mir->ssa_rep->defs[0] = live_def; + int* incoming = reinterpret_cast(phi->dalvikInsn.vB); + for (int i = 0; i < phi->ssa_rep->num_uses; i++) { + if (phi->ssa_rep->uses[i] == live_def) { + incoming[i] = bb->id; + } + } + for (int i = 0; i < phi->ssa_rep->num_uses; i++) { + if (phi->ssa_rep->uses[i] == dead_def) { + int last_slot = phi->ssa_rep->num_uses - 1; + phi->ssa_rep->uses[i] = phi->ssa_rep->uses[last_slot]; + incoming[i] = incoming[last_slot]; + } + } + } + phi->ssa_rep->num_uses--; + bb->taken = NULL; + tk->block_type = kDead; + for (MIR* tmir = ft->first_mir_insn; tmir != NULL; tmir = tmir->next) { + tmir->dalvikInsn.opcode = static_cast(kMirOpNop); + } + } + } + } + } + } + bb = NextDominatedBlock(bb); + } + + if (num_temps > cu_->num_compiler_temps) { + cu_->num_compiler_temps = num_temps; + } + return true; +} + +void MIRGraph::NullCheckEliminationInit(struct BasicBlock* bb) +{ + if (bb->data_flow_info != NULL) { + bb->data_flow_info->ending_null_check_v = + new (arena_) ArenaBitVector(arena_, GetNumSSARegs(), false, kBitMapNullCheck); + } +} + +/* Collect stats on number of checks removed */ +void MIRGraph::CountChecks(struct BasicBlock* bb) +{ + if (bb->data_flow_info != NULL) { + for (MIR* mir = bb->first_mir_insn; mir != NULL; mir = mir->next) { + if (mir->ssa_rep == NULL) { + continue; + } + int df_attributes = oat_data_flow_attributes_[mir->dalvikInsn.opcode]; + if (df_attributes & DF_HAS_NULL_CHKS) { + checkstats_->null_checks++; + if (mir->optimization_flags & MIR_IGNORE_NULL_CHECK) { + checkstats_->null_checks_eliminated++; + } + } + if (df_attributes & DF_HAS_RANGE_CHKS) { + checkstats_->range_checks++; + if (mir->optimization_flags & MIR_IGNORE_RANGE_CHECK) { + checkstats_->range_checks_eliminated++; + } + } + } + } +} + +/* Try to make common case the fallthrough path */ +static bool LayoutBlocks(struct BasicBlock* bb) +{ + // TODO: For now, just looking for direct throws. Consider generalizing for profile feedback + if (!bb->explicit_throw) { + return false; + } + BasicBlock* walker = bb; + while (true) { + // Check termination conditions + if ((walker->block_type == kEntryBlock) || (Predecessors(walker) != 1)) { + break; + } + BasicBlock* prev = walker->predecessors->Get(0); + if (prev->conditional_branch) { + if (prev->fall_through == walker) { + // Already done - return + break; + } + DCHECK_EQ(walker, prev->taken); + // Got one. Flip it and exit + Instruction::Code opcode = prev->last_mir_insn->dalvikInsn.opcode; + switch (opcode) { + case Instruction::IF_EQ: opcode = Instruction::IF_NE; break; + case Instruction::IF_NE: opcode = Instruction::IF_EQ; break; + case Instruction::IF_LT: opcode = Instruction::IF_GE; break; + case Instruction::IF_GE: opcode = Instruction::IF_LT; break; + case Instruction::IF_GT: opcode = Instruction::IF_LE; break; + case Instruction::IF_LE: opcode = Instruction::IF_GT; break; + case Instruction::IF_EQZ: opcode = Instruction::IF_NEZ; break; + case Instruction::IF_NEZ: opcode = Instruction::IF_EQZ; break; + case Instruction::IF_LTZ: opcode = Instruction::IF_GEZ; break; + case Instruction::IF_GEZ: opcode = Instruction::IF_LTZ; break; + case Instruction::IF_GTZ: opcode = Instruction::IF_LEZ; break; + case Instruction::IF_LEZ: opcode = Instruction::IF_GTZ; break; + default: LOG(FATAL) << "Unexpected opcode " << opcode; + } + prev->last_mir_insn->dalvikInsn.opcode = opcode; + BasicBlock* t_bb = prev->taken; + prev->taken = prev->fall_through; + prev->fall_through = t_bb; + break; + } + walker = prev; + } + return false; +} + +/* Combine any basic blocks terminated by instructions that we now know can't throw */ +bool MIRGraph::CombineBlocks(struct BasicBlock* bb) +{ + // Loop here to allow combining a sequence of blocks + while (true) { + // Check termination conditions + if ((bb->first_mir_insn == NULL) + || (bb->data_flow_info == NULL) + || (bb->block_type == kExceptionHandling) + || (bb->block_type == kExitBlock) + || (bb->block_type == kDead) + || ((bb->taken == NULL) || (bb->taken->block_type != kExceptionHandling)) + || (bb->successor_block_list.block_list_type != kNotUsed) + || (static_cast(bb->last_mir_insn->dalvikInsn.opcode) != kMirOpCheck)) { + break; + } + + // Test the kMirOpCheck instruction + MIR* mir = bb->last_mir_insn; + // Grab the attributes from the paired opcode + MIR* throw_insn = mir->meta.throw_insn; + int df_attributes = oat_data_flow_attributes_[throw_insn->dalvikInsn.opcode]; + bool can_combine = true; + if (df_attributes & DF_HAS_NULL_CHKS) { + can_combine &= ((throw_insn->optimization_flags & MIR_IGNORE_NULL_CHECK) != 0); + } + if (df_attributes & DF_HAS_RANGE_CHKS) { + can_combine &= ((throw_insn->optimization_flags & MIR_IGNORE_RANGE_CHECK) != 0); + } + if (!can_combine) { + break; + } + // OK - got one. Combine + BasicBlock* bb_next = bb->fall_through; + DCHECK(!bb_next->catch_entry); + DCHECK_EQ(Predecessors(bb_next), 1U); + MIR* t_mir = bb->last_mir_insn->prev; + // Overwrite the kOpCheck insn with the paired opcode + DCHECK_EQ(bb_next->first_mir_insn, throw_insn); + *bb->last_mir_insn = *throw_insn; + bb->last_mir_insn->prev = t_mir; + // Use the successor info from the next block + bb->successor_block_list = bb_next->successor_block_list; + // Use the ending block linkage from the next block + bb->fall_through = bb_next->fall_through; + bb->taken->block_type = kDead; // Kill the unused exception block + bb->taken = bb_next->taken; + // Include the rest of the instructions + bb->last_mir_insn = bb_next->last_mir_insn; + /* + * If lower-half of pair of blocks to combine contained a return, move the flag + * to the newly combined block. + */ + bb->terminated_by_return = bb_next->terminated_by_return; + + /* + * NOTE: we aren't updating all dataflow info here. Should either make sure this pass + * happens after uses of i_dominated, dom_frontier or update the dataflow info here. + */ + + // Kill bb_next and remap now-dead id to parent + bb_next->block_type = kDead; + block_id_map_.Overwrite(bb_next->id, bb->id); + + // Now, loop back and see if we can keep going + } + return false; +} + +/* Eliminate unnecessary null checks for a basic block. */ +bool MIRGraph::EliminateNullChecks(struct BasicBlock* bb) +{ + if (bb->data_flow_info == NULL) return false; + + /* + * Set initial state. Be conservative with catch + * blocks and start with no assumptions about null check + * status (except for "this"). + */ + if ((bb->block_type == kEntryBlock) | bb->catch_entry) { + temp_ssa_register_v_->ClearAllBits(); + if ((cu_->access_flags & kAccStatic) == 0) { + // If non-static method, mark "this" as non-null + int this_reg = cu_->num_dalvik_registers - cu_->num_ins; + temp_ssa_register_v_->SetBit(this_reg); + } + } else { + // Starting state is intesection of all incoming arcs + GrowableArray::Iterator iter(bb->predecessors); + BasicBlock* pred_bb = iter.Next(); + DCHECK(pred_bb != NULL); + temp_ssa_register_v_->Copy(pred_bb->data_flow_info->ending_null_check_v); + while (true) { + pred_bb = iter.Next(); + if (!pred_bb) break; + if ((pred_bb->data_flow_info == NULL) || + (pred_bb->data_flow_info->ending_null_check_v == NULL)) { + continue; + } + temp_ssa_register_v_->Intersect(pred_bb->data_flow_info->ending_null_check_v); + } + } + + // Walk through the instruction in the block, updating as necessary + for (MIR* mir = bb->first_mir_insn; mir != NULL; mir = mir->next) { + if (mir->ssa_rep == NULL) { + continue; + } + int df_attributes = oat_data_flow_attributes_[mir->dalvikInsn.opcode]; + + // Mark target of NEW* as non-null + if (df_attributes & DF_NON_NULL_DST) { + temp_ssa_register_v_->SetBit(mir->ssa_rep->defs[0]); + } + + // Mark non-null returns from invoke-style NEW* + if (df_attributes & DF_NON_NULL_RET) { + MIR* next_mir = mir->next; + // Next should be an MOVE_RESULT_OBJECT + if (next_mir && + next_mir->dalvikInsn.opcode == Instruction::MOVE_RESULT_OBJECT) { + // Mark as null checked + temp_ssa_register_v_->SetBit(next_mir->ssa_rep->defs[0]); + } else { + if (next_mir) { + LOG(WARNING) << "Unexpected opcode following new: " << next_mir->dalvikInsn.opcode; + } else if (bb->fall_through) { + // Look in next basic block + struct BasicBlock* next_bb = bb->fall_through; + for (MIR* tmir = next_bb->first_mir_insn; tmir != NULL; + tmir =tmir->next) { + if (static_cast(tmir->dalvikInsn.opcode) >= static_cast(kMirOpFirst)) { + continue; + } + // First non-pseudo should be MOVE_RESULT_OBJECT + if (tmir->dalvikInsn.opcode == Instruction::MOVE_RESULT_OBJECT) { + // Mark as null checked + temp_ssa_register_v_->SetBit(tmir->ssa_rep->defs[0]); + } else { + LOG(WARNING) << "Unexpected op after new: " << tmir->dalvikInsn.opcode; + } + break; + } + } + } + } + + /* + * Propagate nullcheck state on register copies (including + * Phi pseudo copies. For the latter, nullcheck state is + * the "and" of all the Phi's operands. + */ + if (df_attributes & (DF_NULL_TRANSFER_0 | DF_NULL_TRANSFER_N)) { + int tgt_sreg = mir->ssa_rep->defs[0]; + int operands = (df_attributes & DF_NULL_TRANSFER_0) ? 1 : + mir->ssa_rep->num_uses; + bool null_checked = true; + for (int i = 0; i < operands; i++) { + null_checked &= temp_ssa_register_v_->IsBitSet(mir->ssa_rep->uses[i]); + } + if (null_checked) { + temp_ssa_register_v_->SetBit(tgt_sreg); + } + } + + // Already nullchecked? + if ((df_attributes & DF_HAS_NULL_CHKS) && !(mir->optimization_flags & MIR_IGNORE_NULL_CHECK)) { + int src_idx; + if (df_attributes & DF_NULL_CHK_1) { + src_idx = 1; + } else if (df_attributes & DF_NULL_CHK_2) { + src_idx = 2; + } else { + src_idx = 0; + } + int src_sreg = mir->ssa_rep->uses[src_idx]; + if (temp_ssa_register_v_->IsBitSet(src_sreg)) { + // Eliminate the null check + mir->optimization_flags |= MIR_IGNORE_NULL_CHECK; + } else { + // Mark s_reg as null-checked + temp_ssa_register_v_->SetBit(src_sreg); + } + } + } + + // Did anything change? + bool changed = !temp_ssa_register_v_->Equal(bb->data_flow_info->ending_null_check_v); + if (changed) { + bb->data_flow_info->ending_null_check_v->Copy(temp_ssa_register_v_); + } + return changed; +} + +void MIRGraph::NullCheckElimination() +{ + if (!(cu_->disable_opt & (1 << kNullCheckElimination))) { + DCHECK(temp_ssa_register_v_ != NULL); + AllNodesIterator iter(this, false /* not iterative */); + for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { + NullCheckEliminationInit(bb); + } + PreOrderDfsIterator iter2(this, true /* iterative */); + bool change = false; + for (BasicBlock* bb = iter2.Next(change); bb != NULL; bb = iter2.Next(change)) { + change = EliminateNullChecks(bb); + } + } + if (cu_->enable_debug & (1 << kDebugDumpCFG)) { + DumpCFG("/sdcard/4_post_nce_cfg/", false); + } +} + +void MIRGraph::BasicBlockCombine() +{ + PreOrderDfsIterator iter(this, false /* not iterative */); + for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { + CombineBlocks(bb); + } + if (cu_->enable_debug & (1 << kDebugDumpCFG)) { + DumpCFG("/sdcard/5_post_bbcombine_cfg/", false); + } +} + +void MIRGraph::CodeLayout() +{ + if (cu_->enable_debug & (1 << kDebugVerifyDataflow)) { + VerifyDataflow(); + } + AllNodesIterator iter(this, false /* not iterative */); + for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { + LayoutBlocks(bb); + } + if (cu_->enable_debug & (1 << kDebugDumpCFG)) { + DumpCFG("/sdcard/2_post_layout_cfg/", true); + } +} + +void MIRGraph::DumpCheckStats() +{ + Checkstats* stats = + static_cast(arena_->NewMem(sizeof(Checkstats), true, + ArenaAllocator::kAllocDFInfo)); + checkstats_ = stats; + AllNodesIterator iter(this, false /* not iterative */); + for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { + CountChecks(bb); + } + if (stats->null_checks > 0) { + float eliminated = static_cast(stats->null_checks_eliminated); + float checks = static_cast(stats->null_checks); + LOG(INFO) << "Null Checks: " << PrettyMethod(cu_->method_idx, *cu_->dex_file) << " " + << stats->null_checks_eliminated << " of " << stats->null_checks << " -> " + << (eliminated/checks) * 100.0 << "%"; + } + if (stats->range_checks > 0) { + float eliminated = static_cast(stats->range_checks_eliminated); + float checks = static_cast(stats->range_checks); + LOG(INFO) << "Range Checks: " << PrettyMethod(cu_->method_idx, *cu_->dex_file) << " " + << stats->range_checks_eliminated << " of " << stats->range_checks << " -> " + << (eliminated/checks) * 100.0 << "%"; + } +} + +bool MIRGraph::BuildExtendedBBList(struct BasicBlock* bb) +{ + if (bb->visited) return false; + if (!((bb->block_type == kEntryBlock) || (bb->block_type == kDalvikByteCode) + || (bb->block_type == kExitBlock))) { + // Ignore special blocks + bb->visited = true; + return false; + } + // Must be head of extended basic block. + BasicBlock* start_bb = bb; + extended_basic_blocks_.push_back(bb); + bool terminated_by_return = false; + // Visit blocks strictly dominated by this head. + while (bb != NULL) { + bb->visited = true; + terminated_by_return |= bb->terminated_by_return; + bb = NextDominatedBlock(bb); + } + if (terminated_by_return) { + // This extended basic block contains a return, so mark all members. + bb = start_bb; + while (bb != NULL) { + bb->dominates_return = true; + bb = NextDominatedBlock(bb); + } + } + return false; // Not iterative - return value will be ignored +} + + +void MIRGraph::BasicBlockOptimization() +{ + if (!(cu_->disable_opt & (1 << kBBOpt))) { + DCHECK_EQ(cu_->num_compiler_temps, 0); + ClearAllVisitedFlags(); + PreOrderDfsIterator iter2(this, false /* not iterative */); + for (BasicBlock* bb = iter2.Next(); bb != NULL; bb = iter2.Next()) { + BuildExtendedBBList(bb); + } + // Perform extended basic block optimizations. + for (unsigned int i = 0; i < extended_basic_blocks_.size(); i++) { + BasicBlockOpt(extended_basic_blocks_[i]); + } + } + if (cu_->enable_debug & (1 << kDebugDumpCFG)) { + DumpCFG("/sdcard/6_post_bbo_cfg/", false); + } +} + +} // namespace art diff --git a/src/compiler/dex/portable/mir_to_gbc.cc b/src/compiler/dex/portable/mir_to_gbc.cc new file mode 100644 index 0000000000000000000000000000000000000000..6fccb47d9ffafb3fbb312f887a469781f513730c --- /dev/null +++ b/src/compiler/dex/portable/mir_to_gbc.cc @@ -0,0 +1,2056 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "object_utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compiler/dex/compiler_internals.h" +#include "compiler/dex/dataflow_iterator.h" +#include "compiler/dex/frontend.h" +#include "mir_to_gbc.h" + +#include "compiler/llvm/llvm_compilation_unit.h" +#include "compiler/llvm/utils_llvm.h" + +const char* kLabelFormat = "%c0x%x_%d"; +const char kInvalidBlock = 0xff; +const char kNormalBlock = 'L'; +const char kCatchBlock = 'C'; + +namespace art { + +::llvm::BasicBlock* MirConverter::GetLLVMBlock(int id) +{ + return id_to_block_map_.Get(id); +} + +::llvm::Value* MirConverter::GetLLVMValue(int s_reg) +{ + return llvm_values_.Get(s_reg); +} + +void MirConverter::SetVregOnValue(::llvm::Value* val, int s_reg) +{ + // Set vreg for debugging + art::llvm::IntrinsicHelper::IntrinsicId id = art::llvm::IntrinsicHelper::SetVReg; + ::llvm::Function* func = intrinsic_helper_->GetIntrinsicFunction(id); + int v_reg = mir_graph_->SRegToVReg(s_reg); + ::llvm::Value* table_slot = irb_->getInt32(v_reg); + ::llvm::Value* args[] = { table_slot, val }; + irb_->CreateCall(func, args); +} + +// Replace the placeholder value with the real definition +void MirConverter::DefineValueOnly(::llvm::Value* val, int s_reg) +{ + ::llvm::Value* placeholder = GetLLVMValue(s_reg); + if (placeholder == NULL) { + // This can happen on instruction rewrite on verification failure + LOG(WARNING) << "Null placeholder"; + return; + } + placeholder->replaceAllUsesWith(val); + val->takeName(placeholder); + llvm_values_.Put(s_reg, val); + ::llvm::Instruction* inst = ::llvm::dyn_cast< ::llvm::Instruction>(placeholder); + DCHECK(inst != NULL); + inst->eraseFromParent(); + +} + +void MirConverter::DefineValue(::llvm::Value* val, int s_reg) +{ + DefineValueOnly(val, s_reg); + SetVregOnValue(val, s_reg); +} + +::llvm::Type* MirConverter::LlvmTypeFromLocRec(RegLocation loc) +{ + ::llvm::Type* res = NULL; + if (loc.wide) { + if (loc.fp) + res = irb_->getDoubleTy(); + else + res = irb_->getInt64Ty(); + } else { + if (loc.fp) { + res = irb_->getFloatTy(); + } else { + if (loc.ref) + res = irb_->getJObjectTy(); + else + res = irb_->getInt32Ty(); + } + } + return res; +} + +void MirConverter::InitIR() +{ + if (llvm_info_ == NULL) { + CompilerTls* tls = cu_->compiler_driver->GetTls(); + CHECK(tls != NULL); + llvm_info_ = static_cast(tls->GetLLVMInfo()); + if (llvm_info_ == NULL) { + llvm_info_ = new LLVMInfo(); + tls->SetLLVMInfo(llvm_info_); + } + } + context_ = llvm_info_->GetLLVMContext(); + module_ = llvm_info_->GetLLVMModule(); + intrinsic_helper_ = llvm_info_->GetIntrinsicHelper(); + irb_ = llvm_info_->GetIRBuilder(); +} + +::llvm::BasicBlock* MirConverter::FindCaseTarget(uint32_t vaddr) +{ + BasicBlock* bb = mir_graph_->FindBlock(vaddr); + DCHECK(bb != NULL); + return GetLLVMBlock(bb->id); +} + +void MirConverter::ConvertPackedSwitch(BasicBlock* bb, + int32_t table_offset, RegLocation rl_src) +{ + const Instruction::PackedSwitchPayload* payload = + reinterpret_cast( + cu_->insns + current_dalvik_offset_ + table_offset); + + ::llvm::Value* value = GetLLVMValue(rl_src.orig_sreg); + + ::llvm::SwitchInst* sw = + irb_->CreateSwitch(value, GetLLVMBlock(bb->fall_through->id), + payload->case_count); + + for (uint16_t i = 0; i < payload->case_count; ++i) { + ::llvm::BasicBlock* llvm_bb = + FindCaseTarget(current_dalvik_offset_ + payload->targets[i]); + sw->addCase(irb_->getInt32(payload->first_key + i), llvm_bb); + } + ::llvm::MDNode* switch_node = + ::llvm::MDNode::get(*context_, irb_->getInt32(table_offset)); + sw->setMetadata("SwitchTable", switch_node); + bb->taken = NULL; + bb->fall_through = NULL; +} + +void MirConverter::ConvertSparseSwitch(BasicBlock* bb, + int32_t table_offset, RegLocation rl_src) +{ + const Instruction::SparseSwitchPayload* payload = + reinterpret_cast( + cu_->insns + current_dalvik_offset_ + table_offset); + + const int32_t* keys = payload->GetKeys(); + const int32_t* targets = payload->GetTargets(); + + ::llvm::Value* value = GetLLVMValue(rl_src.orig_sreg); + + ::llvm::SwitchInst* sw = + irb_->CreateSwitch(value, GetLLVMBlock(bb->fall_through->id), + payload->case_count); + + for (size_t i = 0; i < payload->case_count; ++i) { + ::llvm::BasicBlock* llvm_bb = + FindCaseTarget(current_dalvik_offset_ + targets[i]); + sw->addCase(irb_->getInt32(keys[i]), llvm_bb); + } + ::llvm::MDNode* switch_node = + ::llvm::MDNode::get(*context_, irb_->getInt32(table_offset)); + sw->setMetadata("SwitchTable", switch_node); + bb->taken = NULL; + bb->fall_through = NULL; +} + +void MirConverter::ConvertSget(int32_t field_index, + art::llvm::IntrinsicHelper::IntrinsicId id, RegLocation rl_dest) +{ + ::llvm::Constant* field_idx = irb_->getInt32(field_index); + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::Value* res = irb_->CreateCall(intr, field_idx); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertSput(int32_t field_index, + art::llvm::IntrinsicHelper::IntrinsicId id, RegLocation rl_src) +{ + ::llvm::SmallVector< ::llvm::Value*, 2> args; + args.push_back(irb_->getInt32(field_index)); + args.push_back(GetLLVMValue(rl_src.orig_sreg)); + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + irb_->CreateCall(intr, args); +} + +void MirConverter::ConvertFillArrayData(int32_t offset, RegLocation rl_array) +{ + art::llvm::IntrinsicHelper::IntrinsicId id; + id = art::llvm::IntrinsicHelper::HLFillArrayData; + ::llvm::SmallVector< ::llvm::Value*, 2> args; + args.push_back(irb_->getInt32(offset)); + args.push_back(GetLLVMValue(rl_array.orig_sreg)); + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + irb_->CreateCall(intr, args); +} + +::llvm::Value* MirConverter::EmitConst(::llvm::ArrayRef< ::llvm::Value*> src, + RegLocation loc) +{ + art::llvm::IntrinsicHelper::IntrinsicId id; + if (loc.wide) { + if (loc.fp) { + id = art::llvm::IntrinsicHelper::ConstDouble; + } else { + id = art::llvm::IntrinsicHelper::ConstLong; + } + } else { + if (loc.fp) { + id = art::llvm::IntrinsicHelper::ConstFloat; + } else if (loc.ref) { + id = art::llvm::IntrinsicHelper::ConstObj; + } else { + id = art::llvm::IntrinsicHelper::ConstInt; + } + } + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + return irb_->CreateCall(intr, src); +} + +void MirConverter::EmitPopShadowFrame() +{ + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction( + art::llvm::IntrinsicHelper::PopShadowFrame); + irb_->CreateCall(intr); +} + +::llvm::Value* MirConverter::EmitCopy(::llvm::ArrayRef< ::llvm::Value*> src, + RegLocation loc) +{ + art::llvm::IntrinsicHelper::IntrinsicId id; + if (loc.wide) { + if (loc.fp) { + id = art::llvm::IntrinsicHelper::CopyDouble; + } else { + id = art::llvm::IntrinsicHelper::CopyLong; + } + } else { + if (loc.fp) { + id = art::llvm::IntrinsicHelper::CopyFloat; + } else if (loc.ref) { + id = art::llvm::IntrinsicHelper::CopyObj; + } else { + id = art::llvm::IntrinsicHelper::CopyInt; + } + } + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + return irb_->CreateCall(intr, src); +} + +void MirConverter::ConvertMoveException(RegLocation rl_dest) +{ + ::llvm::Function* func = intrinsic_helper_->GetIntrinsicFunction( + art::llvm::IntrinsicHelper::GetException); + ::llvm::Value* res = irb_->CreateCall(func); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertThrow(RegLocation rl_src) +{ + ::llvm::Value* src = GetLLVMValue(rl_src.orig_sreg); + ::llvm::Function* func = intrinsic_helper_->GetIntrinsicFunction( + art::llvm::IntrinsicHelper::HLThrowException); + irb_->CreateCall(func, src); +} + +void MirConverter::ConvertMonitorEnterExit(int opt_flags, + art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_src) +{ + ::llvm::SmallVector< ::llvm::Value*, 2> args; + args.push_back(irb_->getInt32(opt_flags)); + args.push_back(GetLLVMValue(rl_src.orig_sreg)); + ::llvm::Function* func = intrinsic_helper_->GetIntrinsicFunction(id); + irb_->CreateCall(func, args); +} + +void MirConverter::ConvertArrayLength(int opt_flags, + RegLocation rl_dest, RegLocation rl_src) +{ + ::llvm::SmallVector< ::llvm::Value*, 2> args; + args.push_back(irb_->getInt32(opt_flags)); + args.push_back(GetLLVMValue(rl_src.orig_sreg)); + ::llvm::Function* func = intrinsic_helper_->GetIntrinsicFunction( + art::llvm::IntrinsicHelper::OptArrayLength); + ::llvm::Value* res = irb_->CreateCall(func, args); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::EmitSuspendCheck() +{ + art::llvm::IntrinsicHelper::IntrinsicId id = + art::llvm::IntrinsicHelper::CheckSuspend; + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + irb_->CreateCall(intr); +} + +::llvm::Value* MirConverter::ConvertCompare(ConditionCode cc, + ::llvm::Value* src1, ::llvm::Value* src2) +{ + ::llvm::Value* res = NULL; + DCHECK_EQ(src1->getType(), src2->getType()); + switch(cc) { + case kCondEq: res = irb_->CreateICmpEQ(src1, src2); break; + case kCondNe: res = irb_->CreateICmpNE(src1, src2); break; + case kCondLt: res = irb_->CreateICmpSLT(src1, src2); break; + case kCondGe: res = irb_->CreateICmpSGE(src1, src2); break; + case kCondGt: res = irb_->CreateICmpSGT(src1, src2); break; + case kCondLe: res = irb_->CreateICmpSLE(src1, src2); break; + default: LOG(FATAL) << "Unexpected cc value " << cc; + } + return res; +} + +void MirConverter::ConvertCompareAndBranch(BasicBlock* bb, MIR* mir, + ConditionCode cc, RegLocation rl_src1, RegLocation rl_src2) +{ + if (bb->taken->start_offset <= mir->offset) { + EmitSuspendCheck(); + } + ::llvm::Value* src1 = GetLLVMValue(rl_src1.orig_sreg); + ::llvm::Value* src2 = GetLLVMValue(rl_src2.orig_sreg); + ::llvm::Value* cond_value = ConvertCompare(cc, src1, src2); + cond_value->setName(StringPrintf("t%d", temp_name_++)); + irb_->CreateCondBr(cond_value, GetLLVMBlock(bb->taken->id), + GetLLVMBlock(bb->fall_through->id)); + // Don't redo the fallthrough branch in the BB driver + bb->fall_through = NULL; +} + +void MirConverter::ConvertCompareZeroAndBranch(BasicBlock* bb, + MIR* mir, ConditionCode cc, RegLocation rl_src1) +{ + if (bb->taken->start_offset <= mir->offset) { + EmitSuspendCheck(); + } + ::llvm::Value* src1 = GetLLVMValue(rl_src1.orig_sreg); + ::llvm::Value* src2; + if (rl_src1.ref) { + src2 = irb_->getJNull(); + } else { + src2 = irb_->getInt32(0); + } + ::llvm::Value* cond_value = ConvertCompare(cc, src1, src2); + irb_->CreateCondBr(cond_value, GetLLVMBlock(bb->taken->id), + GetLLVMBlock(bb->fall_through->id)); + // Don't redo the fallthrough branch in the BB driver + bb->fall_through = NULL; +} + +::llvm::Value* MirConverter::GenDivModOp(bool is_div, bool is_long, + ::llvm::Value* src1, ::llvm::Value* src2) +{ + art::llvm::IntrinsicHelper::IntrinsicId id; + if (is_long) { + if (is_div) { + id = art::llvm::IntrinsicHelper::DivLong; + } else { + id = art::llvm::IntrinsicHelper::RemLong; + } + } else { + if (is_div) { + id = art::llvm::IntrinsicHelper::DivInt; + } else { + id = art::llvm::IntrinsicHelper::RemInt; + } + } + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::SmallVector< ::llvm::Value*, 2>args; + args.push_back(src1); + args.push_back(src2); + return irb_->CreateCall(intr, args); +} + +::llvm::Value* MirConverter::GenArithOp(OpKind op, bool is_long, + ::llvm::Value* src1, ::llvm::Value* src2) +{ + ::llvm::Value* res = NULL; + switch(op) { + case kOpAdd: res = irb_->CreateAdd(src1, src2); break; + case kOpSub: res = irb_->CreateSub(src1, src2); break; + case kOpRsub: res = irb_->CreateSub(src2, src1); break; + case kOpMul: res = irb_->CreateMul(src1, src2); break; + case kOpOr: res = irb_->CreateOr(src1, src2); break; + case kOpAnd: res = irb_->CreateAnd(src1, src2); break; + case kOpXor: res = irb_->CreateXor(src1, src2); break; + case kOpDiv: res = GenDivModOp(true, is_long, src1, src2); break; + case kOpRem: res = GenDivModOp(false, is_long, src1, src2); break; + case kOpLsl: res = irb_->CreateShl(src1, src2); break; + case kOpLsr: res = irb_->CreateLShr(src1, src2); break; + case kOpAsr: res = irb_->CreateAShr(src1, src2); break; + default: + LOG(FATAL) << "Invalid op " << op; + } + return res; +} + +void MirConverter::ConvertFPArithOp(OpKind op, RegLocation rl_dest, + RegLocation rl_src1, RegLocation rl_src2) +{ + ::llvm::Value* src1 = GetLLVMValue(rl_src1.orig_sreg); + ::llvm::Value* src2 = GetLLVMValue(rl_src2.orig_sreg); + ::llvm::Value* res = NULL; + switch(op) { + case kOpAdd: res = irb_->CreateFAdd(src1, src2); break; + case kOpSub: res = irb_->CreateFSub(src1, src2); break; + case kOpMul: res = irb_->CreateFMul(src1, src2); break; + case kOpDiv: res = irb_->CreateFDiv(src1, src2); break; + case kOpRem: res = irb_->CreateFRem(src1, src2); break; + default: + LOG(FATAL) << "Invalid op " << op; + } + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertShift(art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_dest, RegLocation rl_src1, RegLocation rl_src2) +{ + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::SmallVector< ::llvm::Value*, 2>args; + args.push_back(GetLLVMValue(rl_src1.orig_sreg)); + args.push_back(GetLLVMValue(rl_src2.orig_sreg)); + ::llvm::Value* res = irb_->CreateCall(intr, args); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertShiftLit(art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_dest, RegLocation rl_src, int shift_amount) +{ + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::SmallVector< ::llvm::Value*, 2>args; + args.push_back(GetLLVMValue(rl_src.orig_sreg)); + args.push_back(irb_->getInt32(shift_amount)); + ::llvm::Value* res = irb_->CreateCall(intr, args); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertArithOp(OpKind op, RegLocation rl_dest, + RegLocation rl_src1, RegLocation rl_src2) +{ + ::llvm::Value* src1 = GetLLVMValue(rl_src1.orig_sreg); + ::llvm::Value* src2 = GetLLVMValue(rl_src2.orig_sreg); + DCHECK_EQ(src1->getType(), src2->getType()); + ::llvm::Value* res = GenArithOp(op, rl_dest.wide, src1, src2); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertArithOpLit(OpKind op, RegLocation rl_dest, + RegLocation rl_src1, int32_t imm) +{ + ::llvm::Value* src1 = GetLLVMValue(rl_src1.orig_sreg); + ::llvm::Value* src2 = irb_->getInt32(imm); + ::llvm::Value* res = GenArithOp(op, rl_dest.wide, src1, src2); + DefineValue(res, rl_dest.orig_sreg); +} + +/* + * Process arguments for invoke. Note: this code is also used to + * collect and process arguments for NEW_FILLED_ARRAY and NEW_FILLED_ARRAY_RANGE. + * The requirements are similar. + */ +void MirConverter::ConvertInvoke(BasicBlock* bb, MIR* mir, + InvokeType invoke_type, bool is_range, bool is_filled_new_array) +{ + CallInfo* info = mir_graph_->NewMemCallInfo(bb, mir, invoke_type, is_range); + ::llvm::SmallVector< ::llvm::Value*, 10> args; + // Insert the invoke_type + args.push_back(irb_->getInt32(static_cast(invoke_type))); + // Insert the method_idx + args.push_back(irb_->getInt32(info->index)); + // Insert the optimization flags + args.push_back(irb_->getInt32(info->opt_flags)); + // Now, insert the actual arguments + for (int i = 0; i < info->num_arg_words;) { + ::llvm::Value* val = GetLLVMValue(info->args[i].orig_sreg); + args.push_back(val); + i += info->args[i].wide ? 2 : 1; + } + /* + * Choose the invoke return type based on actual usage. Note: may + * be different than shorty. For example, if a function return value + * is not used, we'll treat this as a void invoke. + */ + art::llvm::IntrinsicHelper::IntrinsicId id; + if (is_filled_new_array) { + id = art::llvm::IntrinsicHelper::HLFilledNewArray; + } else if (info->result.location == kLocInvalid) { + id = art::llvm::IntrinsicHelper::HLInvokeVoid; + } else { + if (info->result.wide) { + if (info->result.fp) { + id = art::llvm::IntrinsicHelper::HLInvokeDouble; + } else { + id = art::llvm::IntrinsicHelper::HLInvokeLong; + } + } else if (info->result.ref) { + id = art::llvm::IntrinsicHelper::HLInvokeObj; + } else if (info->result.fp) { + id = art::llvm::IntrinsicHelper::HLInvokeFloat; + } else { + id = art::llvm::IntrinsicHelper::HLInvokeInt; + } + } + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::Value* res = irb_->CreateCall(intr, args); + if (info->result.location != kLocInvalid) { + DefineValue(res, info->result.orig_sreg); + } +} + +void MirConverter::ConvertConstObject(uint32_t idx, + art::llvm::IntrinsicHelper::IntrinsicId id, RegLocation rl_dest) +{ + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::Value* index = irb_->getInt32(idx); + ::llvm::Value* res = irb_->CreateCall(intr, index); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertCheckCast(uint32_t type_idx, RegLocation rl_src) +{ + art::llvm::IntrinsicHelper::IntrinsicId id; + id = art::llvm::IntrinsicHelper::HLCheckCast; + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::SmallVector< ::llvm::Value*, 2> args; + args.push_back(irb_->getInt32(type_idx)); + args.push_back(GetLLVMValue(rl_src.orig_sreg)); + irb_->CreateCall(intr, args); +} + +void MirConverter::ConvertNewInstance(uint32_t type_idx, RegLocation rl_dest) +{ + art::llvm::IntrinsicHelper::IntrinsicId id; + id = art::llvm::IntrinsicHelper::NewInstance; + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::Value* index = irb_->getInt32(type_idx); + ::llvm::Value* res = irb_->CreateCall(intr, index); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertNewArray(uint32_t type_idx, + RegLocation rl_dest, RegLocation rl_src) +{ + art::llvm::IntrinsicHelper::IntrinsicId id; + id = art::llvm::IntrinsicHelper::NewArray; + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::SmallVector< ::llvm::Value*, 2> args; + args.push_back(irb_->getInt32(type_idx)); + args.push_back(GetLLVMValue(rl_src.orig_sreg)); + ::llvm::Value* res = irb_->CreateCall(intr, args); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertAget(int opt_flags, + art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_dest, RegLocation rl_array, RegLocation rl_index) +{ + ::llvm::SmallVector< ::llvm::Value*, 3> args; + args.push_back(irb_->getInt32(opt_flags)); + args.push_back(GetLLVMValue(rl_array.orig_sreg)); + args.push_back(GetLLVMValue(rl_index.orig_sreg)); + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::Value* res = irb_->CreateCall(intr, args); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertAput(int opt_flags, + art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_src, RegLocation rl_array, RegLocation rl_index) +{ + ::llvm::SmallVector< ::llvm::Value*, 4> args; + args.push_back(irb_->getInt32(opt_flags)); + args.push_back(GetLLVMValue(rl_src.orig_sreg)); + args.push_back(GetLLVMValue(rl_array.orig_sreg)); + args.push_back(GetLLVMValue(rl_index.orig_sreg)); + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + irb_->CreateCall(intr, args); +} + +void MirConverter::ConvertIget(int opt_flags, + art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_dest, RegLocation rl_obj, int field_index) +{ + ::llvm::SmallVector< ::llvm::Value*, 3> args; + args.push_back(irb_->getInt32(opt_flags)); + args.push_back(GetLLVMValue(rl_obj.orig_sreg)); + args.push_back(irb_->getInt32(field_index)); + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::Value* res = irb_->CreateCall(intr, args); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertIput(int opt_flags, + art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_src, RegLocation rl_obj, int field_index) +{ + ::llvm::SmallVector< ::llvm::Value*, 4> args; + args.push_back(irb_->getInt32(opt_flags)); + args.push_back(GetLLVMValue(rl_src.orig_sreg)); + args.push_back(GetLLVMValue(rl_obj.orig_sreg)); + args.push_back(irb_->getInt32(field_index)); + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + irb_->CreateCall(intr, args); +} + +void MirConverter::ConvertInstanceOf(uint32_t type_idx, + RegLocation rl_dest, RegLocation rl_src) +{ + art::llvm::IntrinsicHelper::IntrinsicId id; + id = art::llvm::IntrinsicHelper::InstanceOf; + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::SmallVector< ::llvm::Value*, 2> args; + args.push_back(irb_->getInt32(type_idx)); + args.push_back(GetLLVMValue(rl_src.orig_sreg)); + ::llvm::Value* res = irb_->CreateCall(intr, args); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertIntToLong(RegLocation rl_dest, RegLocation rl_src) +{ + ::llvm::Value* res = irb_->CreateSExt(GetLLVMValue(rl_src.orig_sreg), + irb_->getInt64Ty()); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertLongToInt(RegLocation rl_dest, RegLocation rl_src) +{ + ::llvm::Value* src = GetLLVMValue(rl_src.orig_sreg); + ::llvm::Value* res = irb_->CreateTrunc(src, irb_->getInt32Ty()); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertFloatToDouble(RegLocation rl_dest, RegLocation rl_src) +{ + ::llvm::Value* src = GetLLVMValue(rl_src.orig_sreg); + ::llvm::Value* res = irb_->CreateFPExt(src, irb_->getDoubleTy()); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertDoubleToFloat(RegLocation rl_dest, RegLocation rl_src) +{ + ::llvm::Value* src = GetLLVMValue(rl_src.orig_sreg); + ::llvm::Value* res = irb_->CreateFPTrunc(src, irb_->getFloatTy()); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertWideComparison(art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_dest, RegLocation rl_src1, + RegLocation rl_src2) +{ + DCHECK_EQ(rl_src1.fp, rl_src2.fp); + DCHECK_EQ(rl_src1.wide, rl_src2.wide); + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::SmallVector< ::llvm::Value*, 2> args; + args.push_back(GetLLVMValue(rl_src1.orig_sreg)); + args.push_back(GetLLVMValue(rl_src2.orig_sreg)); + ::llvm::Value* res = irb_->CreateCall(intr, args); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertIntNarrowing(RegLocation rl_dest, RegLocation rl_src, + art::llvm::IntrinsicHelper::IntrinsicId id) +{ + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::Value* res = + irb_->CreateCall(intr, GetLLVMValue(rl_src.orig_sreg)); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertNeg(RegLocation rl_dest, RegLocation rl_src) +{ + ::llvm::Value* res = irb_->CreateNeg(GetLLVMValue(rl_src.orig_sreg)); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertIntToFP(::llvm::Type* ty, RegLocation rl_dest, + RegLocation rl_src) +{ + ::llvm::Value* res = + irb_->CreateSIToFP(GetLLVMValue(rl_src.orig_sreg), ty); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertFPToInt(art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_dest, + RegLocation rl_src) +{ + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::Value* res = irb_->CreateCall(intr, GetLLVMValue(rl_src.orig_sreg)); + DefineValue(res, rl_dest.orig_sreg); +} + + +void MirConverter::ConvertNegFP(RegLocation rl_dest, RegLocation rl_src) +{ + ::llvm::Value* res = + irb_->CreateFNeg(GetLLVMValue(rl_src.orig_sreg)); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::ConvertNot(RegLocation rl_dest, RegLocation rl_src) +{ + ::llvm::Value* src = GetLLVMValue(rl_src.orig_sreg); + ::llvm::Value* res = irb_->CreateXor(src, static_cast(-1)); + DefineValue(res, rl_dest.orig_sreg); +} + +void MirConverter::EmitConstructorBarrier() { + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction( + art::llvm::IntrinsicHelper::ConstructorBarrier); + irb_->CreateCall(intr); +} + +/* + * Target-independent code generation. Use only high-level + * load/store utilities here, or target-dependent genXX() handlers + * when necessary. + */ +bool MirConverter::ConvertMIRNode(MIR* mir, BasicBlock* bb, + ::llvm::BasicBlock* llvm_bb) +{ + bool res = false; // Assume success + RegLocation rl_src[3]; + RegLocation rl_dest = mir_graph_->GetBadLoc(); + Instruction::Code opcode = mir->dalvikInsn.opcode; + int op_val = opcode; + uint32_t vB = mir->dalvikInsn.vB; + uint32_t vC = mir->dalvikInsn.vC; + int opt_flags = mir->optimization_flags; + + if (cu_->verbose) { + if (op_val < kMirOpFirst) { + LOG(INFO) << ".. " << Instruction::Name(opcode) << " 0x" << std::hex << op_val; + } else { + LOG(INFO) << mir_graph_->extended_mir_op_names_[op_val - kMirOpFirst] << " 0x" << std::hex << op_val; + } + } + + /* Prep Src and Dest locations */ + int next_sreg = 0; + int next_loc = 0; + int attrs = mir_graph_->oat_data_flow_attributes_[opcode]; + rl_src[0] = rl_src[1] = rl_src[2] = mir_graph_->GetBadLoc(); + if (attrs & DF_UA) { + if (attrs & DF_A_WIDE) { + rl_src[next_loc++] = mir_graph_->GetSrcWide(mir, next_sreg); + next_sreg+= 2; + } else { + rl_src[next_loc++] = mir_graph_->GetSrc(mir, next_sreg); + next_sreg++; + } + } + if (attrs & DF_UB) { + if (attrs & DF_B_WIDE) { + rl_src[next_loc++] = mir_graph_->GetSrcWide(mir, next_sreg); + next_sreg+= 2; + } else { + rl_src[next_loc++] = mir_graph_->GetSrc(mir, next_sreg); + next_sreg++; + } + } + if (attrs & DF_UC) { + if (attrs & DF_C_WIDE) { + rl_src[next_loc++] = mir_graph_->GetSrcWide(mir, next_sreg); + } else { + rl_src[next_loc++] = mir_graph_->GetSrc(mir, next_sreg); + } + } + if (attrs & DF_DA) { + if (attrs & DF_A_WIDE) { + rl_dest = mir_graph_->GetDestWide(mir); + } else { + rl_dest = mir_graph_->GetDest(mir); + } + } + + switch (opcode) { + case Instruction::NOP: + break; + + case Instruction::MOVE: + case Instruction::MOVE_OBJECT: + case Instruction::MOVE_16: + case Instruction::MOVE_OBJECT_16: + case Instruction::MOVE_OBJECT_FROM16: + case Instruction::MOVE_FROM16: + case Instruction::MOVE_WIDE: + case Instruction::MOVE_WIDE_16: + case Instruction::MOVE_WIDE_FROM16: { + /* + * Moves/copies are meaningless in pure SSA register form, + * but we need to preserve them for the conversion back into + * MIR (at least until we stop using the Dalvik register maps). + * Insert a dummy intrinsic copy call, which will be recognized + * by the quick path and removed by the portable path. + */ + ::llvm::Value* src = GetLLVMValue(rl_src[0].orig_sreg); + ::llvm::Value* res = EmitCopy(src, rl_dest); + DefineValue(res, rl_dest.orig_sreg); + } + break; + + case Instruction::CONST: + case Instruction::CONST_4: + case Instruction::CONST_16: { + ::llvm::Constant* imm_value = irb_->getJInt(vB); + ::llvm::Value* res = EmitConst(imm_value, rl_dest); + DefineValue(res, rl_dest.orig_sreg); + } + break; + + case Instruction::CONST_WIDE_16: + case Instruction::CONST_WIDE_32: { + // Sign extend to 64 bits + int64_t imm = static_cast(vB); + ::llvm::Constant* imm_value = irb_->getJLong(imm); + ::llvm::Value* res = EmitConst(imm_value, rl_dest); + DefineValue(res, rl_dest.orig_sreg); + } + break; + + case Instruction::CONST_HIGH16: { + ::llvm::Constant* imm_value = irb_->getJInt(vB << 16); + ::llvm::Value* res = EmitConst(imm_value, rl_dest); + DefineValue(res, rl_dest.orig_sreg); + } + break; + + case Instruction::CONST_WIDE: { + ::llvm::Constant* imm_value = + irb_->getJLong(mir->dalvikInsn.vB_wide); + ::llvm::Value* res = EmitConst(imm_value, rl_dest); + DefineValue(res, rl_dest.orig_sreg); + } + break; + case Instruction::CONST_WIDE_HIGH16: { + int64_t imm = static_cast(vB) << 48; + ::llvm::Constant* imm_value = irb_->getJLong(imm); + ::llvm::Value* res = EmitConst(imm_value, rl_dest); + DefineValue(res, rl_dest.orig_sreg); + } + break; + + case Instruction::SPUT_OBJECT: + ConvertSput(vB, art::llvm::IntrinsicHelper::HLSputObject, + rl_src[0]); + break; + case Instruction::SPUT: + if (rl_src[0].fp) { + ConvertSput(vB, art::llvm::IntrinsicHelper::HLSputFloat, + rl_src[0]); + } else { + ConvertSput(vB, art::llvm::IntrinsicHelper::HLSput, rl_src[0]); + } + break; + case Instruction::SPUT_BOOLEAN: + ConvertSput(vB, art::llvm::IntrinsicHelper::HLSputBoolean, + rl_src[0]); + break; + case Instruction::SPUT_BYTE: + ConvertSput(vB, art::llvm::IntrinsicHelper::HLSputByte, rl_src[0]); + break; + case Instruction::SPUT_CHAR: + ConvertSput(vB, art::llvm::IntrinsicHelper::HLSputChar, rl_src[0]); + break; + case Instruction::SPUT_SHORT: + ConvertSput(vB, art::llvm::IntrinsicHelper::HLSputShort, rl_src[0]); + break; + case Instruction::SPUT_WIDE: + if (rl_src[0].fp) { + ConvertSput(vB, art::llvm::IntrinsicHelper::HLSputDouble, + rl_src[0]); + } else { + ConvertSput(vB, art::llvm::IntrinsicHelper::HLSputWide, + rl_src[0]); + } + break; + + case Instruction::SGET_OBJECT: + ConvertSget(vB, art::llvm::IntrinsicHelper::HLSgetObject, rl_dest); + break; + case Instruction::SGET: + if (rl_dest.fp) { + ConvertSget(vB, art::llvm::IntrinsicHelper::HLSgetFloat, rl_dest); + } else { + ConvertSget(vB, art::llvm::IntrinsicHelper::HLSget, rl_dest); + } + break; + case Instruction::SGET_BOOLEAN: + ConvertSget(vB, art::llvm::IntrinsicHelper::HLSgetBoolean, rl_dest); + break; + case Instruction::SGET_BYTE: + ConvertSget(vB, art::llvm::IntrinsicHelper::HLSgetByte, rl_dest); + break; + case Instruction::SGET_CHAR: + ConvertSget(vB, art::llvm::IntrinsicHelper::HLSgetChar, rl_dest); + break; + case Instruction::SGET_SHORT: + ConvertSget(vB, art::llvm::IntrinsicHelper::HLSgetShort, rl_dest); + break; + case Instruction::SGET_WIDE: + if (rl_dest.fp) { + ConvertSget(vB, art::llvm::IntrinsicHelper::HLSgetDouble, + rl_dest); + } else { + ConvertSget(vB, art::llvm::IntrinsicHelper::HLSgetWide, rl_dest); + } + break; + + case Instruction::RETURN_WIDE: + case Instruction::RETURN: + case Instruction::RETURN_OBJECT: { + if (!mir_graph_->MethodIsLeaf()) { + EmitSuspendCheck(); + } + EmitPopShadowFrame(); + irb_->CreateRet(GetLLVMValue(rl_src[0].orig_sreg)); + DCHECK(bb->terminated_by_return); + } + break; + + case Instruction::RETURN_VOID: { + if (((cu_->access_flags & kAccConstructor) != 0) && + cu_->compiler_driver->RequiresConstructorBarrier(Thread::Current(), + cu_->dex_file, + cu_->class_def_idx)) { + EmitConstructorBarrier(); + } + if (!mir_graph_->MethodIsLeaf()) { + EmitSuspendCheck(); + } + EmitPopShadowFrame(); + irb_->CreateRetVoid(); + DCHECK(bb->terminated_by_return); + } + break; + + case Instruction::IF_EQ: + ConvertCompareAndBranch(bb, mir, kCondEq, rl_src[0], rl_src[1]); + break; + case Instruction::IF_NE: + ConvertCompareAndBranch(bb, mir, kCondNe, rl_src[0], rl_src[1]); + break; + case Instruction::IF_LT: + ConvertCompareAndBranch(bb, mir, kCondLt, rl_src[0], rl_src[1]); + break; + case Instruction::IF_GE: + ConvertCompareAndBranch(bb, mir, kCondGe, rl_src[0], rl_src[1]); + break; + case Instruction::IF_GT: + ConvertCompareAndBranch(bb, mir, kCondGt, rl_src[0], rl_src[1]); + break; + case Instruction::IF_LE: + ConvertCompareAndBranch(bb, mir, kCondLe, rl_src[0], rl_src[1]); + break; + case Instruction::IF_EQZ: + ConvertCompareZeroAndBranch(bb, mir, kCondEq, rl_src[0]); + break; + case Instruction::IF_NEZ: + ConvertCompareZeroAndBranch(bb, mir, kCondNe, rl_src[0]); + break; + case Instruction::IF_LTZ: + ConvertCompareZeroAndBranch(bb, mir, kCondLt, rl_src[0]); + break; + case Instruction::IF_GEZ: + ConvertCompareZeroAndBranch(bb, mir, kCondGe, rl_src[0]); + break; + case Instruction::IF_GTZ: + ConvertCompareZeroAndBranch(bb, mir, kCondGt, rl_src[0]); + break; + case Instruction::IF_LEZ: + ConvertCompareZeroAndBranch(bb, mir, kCondLe, rl_src[0]); + break; + + case Instruction::GOTO: + case Instruction::GOTO_16: + case Instruction::GOTO_32: { + if (bb->taken->start_offset <= bb->start_offset) { + EmitSuspendCheck(); + } + irb_->CreateBr(GetLLVMBlock(bb->taken->id)); + } + break; + + case Instruction::ADD_LONG: + case Instruction::ADD_LONG_2ADDR: + case Instruction::ADD_INT: + case Instruction::ADD_INT_2ADDR: + ConvertArithOp(kOpAdd, rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::SUB_LONG: + case Instruction::SUB_LONG_2ADDR: + case Instruction::SUB_INT: + case Instruction::SUB_INT_2ADDR: + ConvertArithOp(kOpSub, rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::MUL_LONG: + case Instruction::MUL_LONG_2ADDR: + case Instruction::MUL_INT: + case Instruction::MUL_INT_2ADDR: + ConvertArithOp(kOpMul, rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::DIV_LONG: + case Instruction::DIV_LONG_2ADDR: + case Instruction::DIV_INT: + case Instruction::DIV_INT_2ADDR: + ConvertArithOp(kOpDiv, rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::REM_LONG: + case Instruction::REM_LONG_2ADDR: + case Instruction::REM_INT: + case Instruction::REM_INT_2ADDR: + ConvertArithOp(kOpRem, rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::AND_LONG: + case Instruction::AND_LONG_2ADDR: + case Instruction::AND_INT: + case Instruction::AND_INT_2ADDR: + ConvertArithOp(kOpAnd, rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::OR_LONG: + case Instruction::OR_LONG_2ADDR: + case Instruction::OR_INT: + case Instruction::OR_INT_2ADDR: + ConvertArithOp(kOpOr, rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::XOR_LONG: + case Instruction::XOR_LONG_2ADDR: + case Instruction::XOR_INT: + case Instruction::XOR_INT_2ADDR: + ConvertArithOp(kOpXor, rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::SHL_LONG: + case Instruction::SHL_LONG_2ADDR: + ConvertShift(art::llvm::IntrinsicHelper::SHLLong, + rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::SHL_INT: + case Instruction::SHL_INT_2ADDR: + ConvertShift(art::llvm::IntrinsicHelper::SHLInt, + rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::SHR_LONG: + case Instruction::SHR_LONG_2ADDR: + ConvertShift(art::llvm::IntrinsicHelper::SHRLong, + rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::SHR_INT: + case Instruction::SHR_INT_2ADDR: + ConvertShift(art::llvm::IntrinsicHelper::SHRInt, + rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::USHR_LONG: + case Instruction::USHR_LONG_2ADDR: + ConvertShift(art::llvm::IntrinsicHelper::USHRLong, + rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::USHR_INT: + case Instruction::USHR_INT_2ADDR: + ConvertShift(art::llvm::IntrinsicHelper::USHRInt, + rl_dest, rl_src[0], rl_src[1]); + break; + + case Instruction::ADD_INT_LIT16: + case Instruction::ADD_INT_LIT8: + ConvertArithOpLit(kOpAdd, rl_dest, rl_src[0], vC); + break; + case Instruction::RSUB_INT: + case Instruction::RSUB_INT_LIT8: + ConvertArithOpLit(kOpRsub, rl_dest, rl_src[0], vC); + break; + case Instruction::MUL_INT_LIT16: + case Instruction::MUL_INT_LIT8: + ConvertArithOpLit(kOpMul, rl_dest, rl_src[0], vC); + break; + case Instruction::DIV_INT_LIT16: + case Instruction::DIV_INT_LIT8: + ConvertArithOpLit(kOpDiv, rl_dest, rl_src[0], vC); + break; + case Instruction::REM_INT_LIT16: + case Instruction::REM_INT_LIT8: + ConvertArithOpLit(kOpRem, rl_dest, rl_src[0], vC); + break; + case Instruction::AND_INT_LIT16: + case Instruction::AND_INT_LIT8: + ConvertArithOpLit(kOpAnd, rl_dest, rl_src[0], vC); + break; + case Instruction::OR_INT_LIT16: + case Instruction::OR_INT_LIT8: + ConvertArithOpLit(kOpOr, rl_dest, rl_src[0], vC); + break; + case Instruction::XOR_INT_LIT16: + case Instruction::XOR_INT_LIT8: + ConvertArithOpLit(kOpXor, rl_dest, rl_src[0], vC); + break; + case Instruction::SHL_INT_LIT8: + ConvertShiftLit(art::llvm::IntrinsicHelper::SHLInt, + rl_dest, rl_src[0], vC & 0x1f); + break; + case Instruction::SHR_INT_LIT8: + ConvertShiftLit(art::llvm::IntrinsicHelper::SHRInt, + rl_dest, rl_src[0], vC & 0x1f); + break; + case Instruction::USHR_INT_LIT8: + ConvertShiftLit(art::llvm::IntrinsicHelper::USHRInt, + rl_dest, rl_src[0], vC & 0x1f); + break; + + case Instruction::ADD_FLOAT: + case Instruction::ADD_FLOAT_2ADDR: + case Instruction::ADD_DOUBLE: + case Instruction::ADD_DOUBLE_2ADDR: + ConvertFPArithOp(kOpAdd, rl_dest, rl_src[0], rl_src[1]); + break; + + case Instruction::SUB_FLOAT: + case Instruction::SUB_FLOAT_2ADDR: + case Instruction::SUB_DOUBLE: + case Instruction::SUB_DOUBLE_2ADDR: + ConvertFPArithOp(kOpSub, rl_dest, rl_src[0], rl_src[1]); + break; + + case Instruction::MUL_FLOAT: + case Instruction::MUL_FLOAT_2ADDR: + case Instruction::MUL_DOUBLE: + case Instruction::MUL_DOUBLE_2ADDR: + ConvertFPArithOp(kOpMul, rl_dest, rl_src[0], rl_src[1]); + break; + + case Instruction::DIV_FLOAT: + case Instruction::DIV_FLOAT_2ADDR: + case Instruction::DIV_DOUBLE: + case Instruction::DIV_DOUBLE_2ADDR: + ConvertFPArithOp(kOpDiv, rl_dest, rl_src[0], rl_src[1]); + break; + + case Instruction::REM_FLOAT: + case Instruction::REM_FLOAT_2ADDR: + case Instruction::REM_DOUBLE: + case Instruction::REM_DOUBLE_2ADDR: + ConvertFPArithOp(kOpRem, rl_dest, rl_src[0], rl_src[1]); + break; + + case Instruction::INVOKE_STATIC: + ConvertInvoke(bb, mir, kStatic, false /*range*/, + false /* NewFilledArray */); + break; + case Instruction::INVOKE_STATIC_RANGE: + ConvertInvoke(bb, mir, kStatic, true /*range*/, + false /* NewFilledArray */); + break; + + case Instruction::INVOKE_DIRECT: + ConvertInvoke(bb, mir, kDirect, false /*range*/, + false /* NewFilledArray */); + break; + case Instruction::INVOKE_DIRECT_RANGE: + ConvertInvoke(bb, mir, kDirect, true /*range*/, + false /* NewFilledArray */); + break; + + case Instruction::INVOKE_VIRTUAL: + ConvertInvoke(bb, mir, kVirtual, false /*range*/, + false /* NewFilledArray */); + break; + case Instruction::INVOKE_VIRTUAL_RANGE: + ConvertInvoke(bb, mir, kVirtual, true /*range*/, + false /* NewFilledArray */); + break; + + case Instruction::INVOKE_SUPER: + ConvertInvoke(bb, mir, kSuper, false /*range*/, + false /* NewFilledArray */); + break; + case Instruction::INVOKE_SUPER_RANGE: + ConvertInvoke(bb, mir, kSuper, true /*range*/, + false /* NewFilledArray */); + break; + + case Instruction::INVOKE_INTERFACE: + ConvertInvoke(bb, mir, kInterface, false /*range*/, + false /* NewFilledArray */); + break; + case Instruction::INVOKE_INTERFACE_RANGE: + ConvertInvoke(bb, mir, kInterface, true /*range*/, + false /* NewFilledArray */); + break; + case Instruction::FILLED_NEW_ARRAY: + ConvertInvoke(bb, mir, kInterface, false /*range*/, + true /* NewFilledArray */); + break; + case Instruction::FILLED_NEW_ARRAY_RANGE: + ConvertInvoke(bb, mir, kInterface, true /*range*/, + true /* NewFilledArray */); + break; + + case Instruction::CONST_STRING: + case Instruction::CONST_STRING_JUMBO: + ConvertConstObject(vB, art::llvm::IntrinsicHelper::ConstString, + rl_dest); + break; + + case Instruction::CONST_CLASS: + ConvertConstObject(vB, art::llvm::IntrinsicHelper::ConstClass, + rl_dest); + break; + + case Instruction::CHECK_CAST: + ConvertCheckCast(vB, rl_src[0]); + break; + + case Instruction::NEW_INSTANCE: + ConvertNewInstance(vB, rl_dest); + break; + + case Instruction::MOVE_EXCEPTION: + ConvertMoveException(rl_dest); + break; + + case Instruction::THROW: + ConvertThrow(rl_src[0]); + /* + * If this throw is standalone, terminate. + * If it might rethrow, force termination + * of the following block. + */ + if (bb->fall_through == NULL) { + irb_->CreateUnreachable(); + } else { + bb->fall_through->fall_through = NULL; + bb->fall_through->taken = NULL; + } + break; + + case Instruction::MOVE_RESULT_WIDE: + case Instruction::MOVE_RESULT: + case Instruction::MOVE_RESULT_OBJECT: + /* + * All move_results should have been folded into the preceeding invoke. + */ + LOG(FATAL) << "Unexpected move_result"; + break; + + case Instruction::MONITOR_ENTER: + ConvertMonitorEnterExit(opt_flags, + art::llvm::IntrinsicHelper::MonitorEnter, + rl_src[0]); + break; + + case Instruction::MONITOR_EXIT: + ConvertMonitorEnterExit(opt_flags, + art::llvm::IntrinsicHelper::MonitorExit, + rl_src[0]); + break; + + case Instruction::ARRAY_LENGTH: + ConvertArrayLength(opt_flags, rl_dest, rl_src[0]); + break; + + case Instruction::NEW_ARRAY: + ConvertNewArray(vC, rl_dest, rl_src[0]); + break; + + case Instruction::INSTANCE_OF: + ConvertInstanceOf(vC, rl_dest, rl_src[0]); + break; + + case Instruction::AGET: + if (rl_dest.fp) { + ConvertAget(opt_flags, + art::llvm::IntrinsicHelper::HLArrayGetFloat, + rl_dest, rl_src[0], rl_src[1]); + } else { + ConvertAget(opt_flags, art::llvm::IntrinsicHelper::HLArrayGet, + rl_dest, rl_src[0], rl_src[1]); + } + break; + case Instruction::AGET_OBJECT: + ConvertAget(opt_flags, art::llvm::IntrinsicHelper::HLArrayGetObject, + rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::AGET_BOOLEAN: + ConvertAget(opt_flags, + art::llvm::IntrinsicHelper::HLArrayGetBoolean, + rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::AGET_BYTE: + ConvertAget(opt_flags, art::llvm::IntrinsicHelper::HLArrayGetByte, + rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::AGET_CHAR: + ConvertAget(opt_flags, art::llvm::IntrinsicHelper::HLArrayGetChar, + rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::AGET_SHORT: + ConvertAget(opt_flags, art::llvm::IntrinsicHelper::HLArrayGetShort, + rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::AGET_WIDE: + if (rl_dest.fp) { + ConvertAget(opt_flags, + art::llvm::IntrinsicHelper::HLArrayGetDouble, + rl_dest, rl_src[0], rl_src[1]); + } else { + ConvertAget(opt_flags, art::llvm::IntrinsicHelper::HLArrayGetWide, + rl_dest, rl_src[0], rl_src[1]); + } + break; + + case Instruction::APUT: + if (rl_src[0].fp) { + ConvertAput(opt_flags, + art::llvm::IntrinsicHelper::HLArrayPutFloat, + rl_src[0], rl_src[1], rl_src[2]); + } else { + ConvertAput(opt_flags, art::llvm::IntrinsicHelper::HLArrayPut, + rl_src[0], rl_src[1], rl_src[2]); + } + break; + case Instruction::APUT_OBJECT: + ConvertAput(opt_flags, art::llvm::IntrinsicHelper::HLArrayPutObject, + rl_src[0], rl_src[1], rl_src[2]); + break; + case Instruction::APUT_BOOLEAN: + ConvertAput(opt_flags, + art::llvm::IntrinsicHelper::HLArrayPutBoolean, + rl_src[0], rl_src[1], rl_src[2]); + break; + case Instruction::APUT_BYTE: + ConvertAput(opt_flags, art::llvm::IntrinsicHelper::HLArrayPutByte, + rl_src[0], rl_src[1], rl_src[2]); + break; + case Instruction::APUT_CHAR: + ConvertAput(opt_flags, art::llvm::IntrinsicHelper::HLArrayPutChar, + rl_src[0], rl_src[1], rl_src[2]); + break; + case Instruction::APUT_SHORT: + ConvertAput(opt_flags, art::llvm::IntrinsicHelper::HLArrayPutShort, + rl_src[0], rl_src[1], rl_src[2]); + break; + case Instruction::APUT_WIDE: + if (rl_src[0].fp) { + ConvertAput(opt_flags, + art::llvm::IntrinsicHelper::HLArrayPutDouble, + rl_src[0], rl_src[1], rl_src[2]); + } else { + ConvertAput(opt_flags, art::llvm::IntrinsicHelper::HLArrayPutWide, + rl_src[0], rl_src[1], rl_src[2]); + } + break; + + case Instruction::IGET: + if (rl_dest.fp) { + ConvertIget(opt_flags, art::llvm::IntrinsicHelper::HLIGetFloat, + rl_dest, rl_src[0], vC); + } else { + ConvertIget(opt_flags, art::llvm::IntrinsicHelper::HLIGet, + rl_dest, rl_src[0], vC); + } + break; + case Instruction::IGET_OBJECT: + ConvertIget(opt_flags, art::llvm::IntrinsicHelper::HLIGetObject, + rl_dest, rl_src[0], vC); + break; + case Instruction::IGET_BOOLEAN: + ConvertIget(opt_flags, art::llvm::IntrinsicHelper::HLIGetBoolean, + rl_dest, rl_src[0], vC); + break; + case Instruction::IGET_BYTE: + ConvertIget(opt_flags, art::llvm::IntrinsicHelper::HLIGetByte, + rl_dest, rl_src[0], vC); + break; + case Instruction::IGET_CHAR: + ConvertIget(opt_flags, art::llvm::IntrinsicHelper::HLIGetChar, + rl_dest, rl_src[0], vC); + break; + case Instruction::IGET_SHORT: + ConvertIget(opt_flags, art::llvm::IntrinsicHelper::HLIGetShort, + rl_dest, rl_src[0], vC); + break; + case Instruction::IGET_WIDE: + if (rl_dest.fp) { + ConvertIget(opt_flags, art::llvm::IntrinsicHelper::HLIGetDouble, + rl_dest, rl_src[0], vC); + } else { + ConvertIget(opt_flags, art::llvm::IntrinsicHelper::HLIGetWide, + rl_dest, rl_src[0], vC); + } + break; + case Instruction::IPUT: + if (rl_src[0].fp) { + ConvertIput(opt_flags, art::llvm::IntrinsicHelper::HLIPutFloat, + rl_src[0], rl_src[1], vC); + } else { + ConvertIput(opt_flags, art::llvm::IntrinsicHelper::HLIPut, + rl_src[0], rl_src[1], vC); + } + break; + case Instruction::IPUT_OBJECT: + ConvertIput(opt_flags, art::llvm::IntrinsicHelper::HLIPutObject, + rl_src[0], rl_src[1], vC); + break; + case Instruction::IPUT_BOOLEAN: + ConvertIput(opt_flags, art::llvm::IntrinsicHelper::HLIPutBoolean, + rl_src[0], rl_src[1], vC); + break; + case Instruction::IPUT_BYTE: + ConvertIput(opt_flags, art::llvm::IntrinsicHelper::HLIPutByte, + rl_src[0], rl_src[1], vC); + break; + case Instruction::IPUT_CHAR: + ConvertIput(opt_flags, art::llvm::IntrinsicHelper::HLIPutChar, + rl_src[0], rl_src[1], vC); + break; + case Instruction::IPUT_SHORT: + ConvertIput(opt_flags, art::llvm::IntrinsicHelper::HLIPutShort, + rl_src[0], rl_src[1], vC); + break; + case Instruction::IPUT_WIDE: + if (rl_src[0].fp) { + ConvertIput(opt_flags, art::llvm::IntrinsicHelper::HLIPutDouble, + rl_src[0], rl_src[1], vC); + } else { + ConvertIput(opt_flags, art::llvm::IntrinsicHelper::HLIPutWide, + rl_src[0], rl_src[1], vC); + } + break; + + case Instruction::FILL_ARRAY_DATA: + ConvertFillArrayData(vB, rl_src[0]); + break; + + case Instruction::LONG_TO_INT: + ConvertLongToInt(rl_dest, rl_src[0]); + break; + + case Instruction::INT_TO_LONG: + ConvertIntToLong(rl_dest, rl_src[0]); + break; + + case Instruction::INT_TO_CHAR: + ConvertIntNarrowing(rl_dest, rl_src[0], + art::llvm::IntrinsicHelper::IntToChar); + break; + case Instruction::INT_TO_BYTE: + ConvertIntNarrowing(rl_dest, rl_src[0], + art::llvm::IntrinsicHelper::IntToByte); + break; + case Instruction::INT_TO_SHORT: + ConvertIntNarrowing(rl_dest, rl_src[0], + art::llvm::IntrinsicHelper::IntToShort); + break; + + case Instruction::INT_TO_FLOAT: + case Instruction::LONG_TO_FLOAT: + ConvertIntToFP(irb_->getFloatTy(), rl_dest, rl_src[0]); + break; + + case Instruction::INT_TO_DOUBLE: + case Instruction::LONG_TO_DOUBLE: + ConvertIntToFP(irb_->getDoubleTy(), rl_dest, rl_src[0]); + break; + + case Instruction::FLOAT_TO_DOUBLE: + ConvertFloatToDouble(rl_dest, rl_src[0]); + break; + + case Instruction::DOUBLE_TO_FLOAT: + ConvertDoubleToFloat(rl_dest, rl_src[0]); + break; + + case Instruction::NEG_LONG: + case Instruction::NEG_INT: + ConvertNeg(rl_dest, rl_src[0]); + break; + + case Instruction::NEG_FLOAT: + case Instruction::NEG_DOUBLE: + ConvertNegFP(rl_dest, rl_src[0]); + break; + + case Instruction::NOT_LONG: + case Instruction::NOT_INT: + ConvertNot(rl_dest, rl_src[0]); + break; + + case Instruction::FLOAT_TO_INT: + ConvertFPToInt(art::llvm::IntrinsicHelper::F2I, rl_dest, rl_src[0]); + break; + + case Instruction::DOUBLE_TO_INT: + ConvertFPToInt(art::llvm::IntrinsicHelper::D2I, rl_dest, rl_src[0]); + break; + + case Instruction::FLOAT_TO_LONG: + ConvertFPToInt(art::llvm::IntrinsicHelper::F2L, rl_dest, rl_src[0]); + break; + + case Instruction::DOUBLE_TO_LONG: + ConvertFPToInt(art::llvm::IntrinsicHelper::D2L, rl_dest, rl_src[0]); + break; + + case Instruction::CMPL_FLOAT: + ConvertWideComparison(art::llvm::IntrinsicHelper::CmplFloat, + rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::CMPG_FLOAT: + ConvertWideComparison(art::llvm::IntrinsicHelper::CmpgFloat, + rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::CMPL_DOUBLE: + ConvertWideComparison(art::llvm::IntrinsicHelper::CmplDouble, + rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::CMPG_DOUBLE: + ConvertWideComparison(art::llvm::IntrinsicHelper::CmpgDouble, + rl_dest, rl_src[0], rl_src[1]); + break; + case Instruction::CMP_LONG: + ConvertWideComparison(art::llvm::IntrinsicHelper::CmpLong, + rl_dest, rl_src[0], rl_src[1]); + break; + + case Instruction::PACKED_SWITCH: + ConvertPackedSwitch(bb, vB, rl_src[0]); + break; + + case Instruction::SPARSE_SWITCH: + ConvertSparseSwitch(bb, vB, rl_src[0]); + break; + + default: + UNIMPLEMENTED(FATAL) << "Unsupported Dex opcode 0x" << std::hex << opcode; + res = true; + } + return res; +} + +void MirConverter::SetDexOffset(int32_t offset) +{ + current_dalvik_offset_ = offset; + ::llvm::SmallVector< ::llvm::Value*, 1> array_ref; + array_ref.push_back(irb_->getInt32(offset)); + ::llvm::MDNode* node = ::llvm::MDNode::get(*context_, array_ref); + irb_->SetDexOffset(node); +} + +// Attach method info as metadata to special intrinsic +void MirConverter::SetMethodInfo() +{ + // We don't want dex offset on this + irb_->SetDexOffset(NULL); + art::llvm::IntrinsicHelper::IntrinsicId id; + id = art::llvm::IntrinsicHelper::MethodInfo; + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::Instruction* inst = irb_->CreateCall(intr); + ::llvm::SmallVector< ::llvm::Value*, 2> reg_info; + reg_info.push_back(irb_->getInt32(cu_->num_ins)); + reg_info.push_back(irb_->getInt32(cu_->num_regs)); + reg_info.push_back(irb_->getInt32(cu_->num_outs)); + reg_info.push_back(irb_->getInt32(cu_->num_compiler_temps)); + reg_info.push_back(irb_->getInt32(mir_graph_->GetNumSSARegs())); + ::llvm::MDNode* reg_info_node = ::llvm::MDNode::get(*context_, reg_info); + inst->setMetadata("RegInfo", reg_info_node); + SetDexOffset(current_dalvik_offset_); +} + +void MirConverter::HandlePhiNodes(BasicBlock* bb, ::llvm::BasicBlock* llvm_bb) +{ + SetDexOffset(bb->start_offset); + for (MIR* mir = bb->first_mir_insn; mir != NULL; mir = mir->next) { + int opcode = mir->dalvikInsn.opcode; + if (opcode < kMirOpFirst) { + // Stop after first non-pseudo MIR op. + continue; + } + if (opcode != kMirOpPhi) { + // Skip other mir Pseudos. + continue; + } + RegLocation rl_dest = mir_graph_->reg_location_[mir->ssa_rep->defs[0]]; + /* + * The Art compiler's Phi nodes only handle 32-bit operands, + * representing wide values using a matched set of Phi nodes + * for the lower and upper halves. In the llvm world, we only + * want a single Phi for wides. Here we will simply discard + * the Phi node representing the high word. + */ + if (rl_dest.high_word) { + continue; // No Phi node - handled via low word + } + int* incoming = reinterpret_cast(mir->dalvikInsn.vB); + ::llvm::Type* phi_type = + LlvmTypeFromLocRec(rl_dest); + ::llvm::PHINode* phi = irb_->CreatePHI(phi_type, mir->ssa_rep->num_uses); + for (int i = 0; i < mir->ssa_rep->num_uses; i++) { + RegLocation loc; + // Don't check width here. + loc = mir_graph_->GetRawSrc(mir, i); + DCHECK_EQ(rl_dest.wide, loc.wide); + DCHECK_EQ(rl_dest.wide & rl_dest.high_word, loc.wide & loc.high_word); + DCHECK_EQ(rl_dest.fp, loc.fp); + DCHECK_EQ(rl_dest.core, loc.core); + DCHECK_EQ(rl_dest.ref, loc.ref); + SafeMap::iterator it; + it = mir_graph_->block_id_map_.find(incoming[i]); + DCHECK(it != mir_graph_->block_id_map_.end()); + DCHECK(GetLLVMValue(loc.orig_sreg) != NULL); + DCHECK(GetLLVMBlock(it->second) != NULL); + phi->addIncoming(GetLLVMValue(loc.orig_sreg), + GetLLVMBlock(it->second)); + } + DefineValueOnly(phi, rl_dest.orig_sreg); + } +} + +/* Extended MIR instructions like PHI */ +void MirConverter::ConvertExtendedMIR(BasicBlock* bb, MIR* mir, + ::llvm::BasicBlock* llvm_bb) +{ + + switch (static_cast(mir->dalvikInsn.opcode)) { + case kMirOpPhi: { + // The llvm Phi node already emitted - just DefineValue() here. + RegLocation rl_dest = mir_graph_->reg_location_[mir->ssa_rep->defs[0]]; + if (!rl_dest.high_word) { + // Only consider low word of pairs. + DCHECK(GetLLVMValue(rl_dest.orig_sreg) != NULL); + ::llvm::Value* phi = GetLLVMValue(rl_dest.orig_sreg); + if (1) SetVregOnValue(phi, rl_dest.orig_sreg); + } + break; + } + case kMirOpCopy: { + UNIMPLEMENTED(WARNING) << "unimp kMirOpPhi"; + break; + } + case kMirOpNop: + if ((mir == bb->last_mir_insn) && (bb->taken == NULL) && + (bb->fall_through == NULL)) { + irb_->CreateUnreachable(); + } + break; + + // TODO: need GBC intrinsic to take advantage of fused operations + case kMirOpFusedCmplFloat: + UNIMPLEMENTED(FATAL) << "kMirOpFusedCmpFloat unsupported"; + break; + case kMirOpFusedCmpgFloat: + UNIMPLEMENTED(FATAL) << "kMirOpFusedCmgFloat unsupported"; + break; + case kMirOpFusedCmplDouble: + UNIMPLEMENTED(FATAL) << "kMirOpFusedCmplDouble unsupported"; + break; + case kMirOpFusedCmpgDouble: + UNIMPLEMENTED(FATAL) << "kMirOpFusedCmpgDouble unsupported"; + break; + case kMirOpFusedCmpLong: + UNIMPLEMENTED(FATAL) << "kMirOpLongCmpBranch unsupported"; + break; + default: + break; + } +} + +/* Handle the content in each basic block */ +bool MirConverter::BlockBitcodeConversion(BasicBlock* bb) +{ + if (bb->block_type == kDead) return false; + ::llvm::BasicBlock* llvm_bb = GetLLVMBlock(bb->id); + if (llvm_bb == NULL) { + CHECK(bb->block_type == kExitBlock); + } else { + irb_->SetInsertPoint(llvm_bb); + SetDexOffset(bb->start_offset); + } + + if (cu_->verbose) { + LOG(INFO) << "................................"; + LOG(INFO) << "Block id " << bb->id; + if (llvm_bb != NULL) { + LOG(INFO) << "label " << llvm_bb->getName().str().c_str(); + } else { + LOG(INFO) << "llvm_bb is NULL"; + } + } + + if (bb->block_type == kEntryBlock) { + SetMethodInfo(); + + { // Allocate shadowframe. + art::llvm::IntrinsicHelper::IntrinsicId id = + art::llvm::IntrinsicHelper::AllocaShadowFrame; + ::llvm::Function* func = intrinsic_helper_->GetIntrinsicFunction(id); + ::llvm::Value* entries = irb_->getInt32(cu_->num_dalvik_registers); + irb_->CreateCall(func, entries); + } + + { // Store arguments to vregs. + uint16_t arg_reg = cu_->num_regs; + + ::llvm::Function::arg_iterator arg_iter(func_->arg_begin()); + ::llvm::Function::arg_iterator arg_end(func_->arg_end()); + + const char* shorty = cu_->shorty; + uint32_t shorty_size = strlen(shorty); + CHECK_GE(shorty_size, 1u); + + ++arg_iter; // skip method object + + if ((cu_->access_flags & kAccStatic) == 0) { + SetVregOnValue(arg_iter, arg_reg); + ++arg_iter; + ++arg_reg; + } + + for (uint32_t i = 1; i < shorty_size; ++i, ++arg_iter) { + SetVregOnValue(arg_iter, arg_reg); + + ++arg_reg; + if (shorty[i] == 'J' || shorty[i] == 'D') { + // Wide types, such as long and double, are using a pair of registers + // to store the value, so we have to increase arg_reg again. + ++arg_reg; + } + } + } + } else if (bb->block_type == kExitBlock) { + /* + * Because of the differences between how MIR/LIR and llvm handle exit + * blocks, we won't explicitly covert them. On the llvm-to-lir + * path, it will need to be regenereated. + */ + return false; + } else if (bb->block_type == kExceptionHandling) { + /* + * Because we're deferring null checking, delete the associated empty + * exception block. + */ + llvm_bb->eraseFromParent(); + return false; + } + + HandlePhiNodes(bb, llvm_bb); + + for (MIR* mir = bb->first_mir_insn; mir != NULL; mir = mir->next) { + + SetDexOffset(mir->offset); + + int opcode = mir->dalvikInsn.opcode; + Instruction::Format dalvik_format = + Instruction::FormatOf(mir->dalvikInsn.opcode); + + if (opcode == kMirOpCheck) { + // Combine check and work halves of throwing instruction. + MIR* work_half = mir->meta.throw_insn; + mir->dalvikInsn.opcode = work_half->dalvikInsn.opcode; + opcode = mir->dalvikInsn.opcode; + SSARepresentation* ssa_rep = work_half->ssa_rep; + work_half->ssa_rep = mir->ssa_rep; + mir->ssa_rep = ssa_rep; + work_half->meta.original_opcode = work_half->dalvikInsn.opcode; + work_half->dalvikInsn.opcode = static_cast(kMirOpNop); + if (bb->successor_block_list.block_list_type == kCatch) { + ::llvm::Function* intr = intrinsic_helper_->GetIntrinsicFunction( + art::llvm::IntrinsicHelper::CatchTargets); + ::llvm::Value* switch_key = + irb_->CreateCall(intr, irb_->getInt32(mir->offset)); + GrowableArray::Iterator iter(bb->successor_block_list.blocks); + // New basic block to use for work half + ::llvm::BasicBlock* work_bb = + ::llvm::BasicBlock::Create(*context_, "", func_); + ::llvm::SwitchInst* sw = + irb_->CreateSwitch(switch_key, work_bb, + bb->successor_block_list.blocks->Size()); + while (true) { + SuccessorBlockInfo *successor_block_info = iter.Next(); + if (successor_block_info == NULL) break; + ::llvm::BasicBlock *target = + GetLLVMBlock(successor_block_info->block->id); + int type_index = successor_block_info->key; + sw->addCase(irb_->getInt32(type_index), target); + } + llvm_bb = work_bb; + irb_->SetInsertPoint(llvm_bb); + } + } + + if (opcode >= kMirOpFirst) { + ConvertExtendedMIR(bb, mir, llvm_bb); + continue; + } + + bool not_handled = ConvertMIRNode(mir, bb, llvm_bb); + if (not_handled) { + Instruction::Code dalvik_opcode = static_cast(opcode); + LOG(WARNING) << StringPrintf("%#06x: Op %#x (%s) / Fmt %d not handled", + mir->offset, opcode, + Instruction::Name(dalvik_opcode), + dalvik_format); + } + } + + if (bb->block_type == kEntryBlock) { + entry_target_bb_ = GetLLVMBlock(bb->fall_through->id); + } else if ((bb->fall_through != NULL) && !bb->terminated_by_return) { + irb_->CreateBr(GetLLVMBlock(bb->fall_through->id)); + } + + return false; +} + +char RemapShorty(char shorty_type) { + /* + * TODO: might want to revisit this. Dalvik registers are 32-bits wide, + * and longs/doubles are represented as a pair of registers. When sub-word + * arguments (and method results) are passed, they are extended to Dalvik + * virtual register containers. Because llvm is picky about type consistency, + * we must either cast the "real" type to 32-bit container multiple Dalvik + * register types, or always use the expanded values. + * Here, we're doing the latter. We map the shorty signature to container + * types (which is valid so long as we always do a real expansion of passed + * arguments and field loads). + */ + switch(shorty_type) { + case 'Z' : shorty_type = 'I'; break; + case 'B' : shorty_type = 'I'; break; + case 'S' : shorty_type = 'I'; break; + case 'C' : shorty_type = 'I'; break; + default: break; + } + return shorty_type; +} + +::llvm::FunctionType* MirConverter::GetFunctionType() { + + // Get return type + ::llvm::Type* ret_type = irb_->getJType(RemapShorty(cu_->shorty[0])); + + // Get argument type + std::vector< ::llvm::Type*> args_type; + + // method object + args_type.push_back(irb_->getJMethodTy()); + + // Do we have a "this"? + if ((cu_->access_flags & kAccStatic) == 0) { + args_type.push_back(irb_->getJObjectTy()); + } + + for (uint32_t i = 1; i < strlen(cu_->shorty); ++i) { + args_type.push_back(irb_->getJType(RemapShorty(cu_->shorty[i]))); + } + + return ::llvm::FunctionType::get(ret_type, args_type, false); +} + +bool MirConverter::CreateFunction() { + ::llvm::FunctionType* func_type = GetFunctionType(); + if (func_type == NULL) { + return false; + } + + func_ = ::llvm::Function::Create(func_type, + ::llvm::Function::InternalLinkage, + symbol_, module_); + + ::llvm::Function::arg_iterator arg_iter(func_->arg_begin()); + ::llvm::Function::arg_iterator arg_end(func_->arg_end()); + + arg_iter->setName("method"); + ++arg_iter; + + int start_sreg = cu_->num_regs; + + for (unsigned i = 0; arg_iter != arg_end; ++i, ++arg_iter) { + arg_iter->setName(StringPrintf("v%i_0", start_sreg)); + start_sreg += mir_graph_->reg_location_[start_sreg].wide ? 2 : 1; + } + + return true; +} + +bool MirConverter::CreateLLVMBasicBlock(BasicBlock* bb) +{ + // Skip the exit block + if ((bb->block_type == kDead) ||(bb->block_type == kExitBlock)) { + id_to_block_map_.Put(bb->id, NULL); + } else { + int offset = bb->start_offset; + bool entry_block = (bb->block_type == kEntryBlock); + ::llvm::BasicBlock* llvm_bb = + ::llvm::BasicBlock::Create(*context_, entry_block ? "entry" : + StringPrintf(kLabelFormat, bb->catch_entry ? kCatchBlock : + kNormalBlock, offset, bb->id), func_); + if (entry_block) { + entry_bb_ = llvm_bb; + placeholder_bb_ = + ::llvm::BasicBlock::Create(*context_, "placeholder", + func_); + } + id_to_block_map_.Put(bb->id, llvm_bb); + } + return false; +} + + +/* + * Convert MIR to LLVM_IR + * o For each ssa name, create LLVM named value. Type these + * appropriately, and ignore high half of wide and double operands. + * o For each MIR basic block, create an LLVM basic block. + * o Iterate through the MIR a basic block at a time, setting arguments + * to recovered ssa name. + */ +void MirConverter::MethodMIR2Bitcode() +{ + InitIR(); + + // Create the function + CreateFunction(); + + // Create an LLVM basic block for each MIR block in dfs preorder + PreOrderDfsIterator iter(mir_graph_, false /* not iterative */); + for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { + CreateLLVMBasicBlock(bb); + } + + /* + * Create an llvm named value for each MIR SSA name. Note: we'll use + * placeholders for all non-argument values (because we haven't seen + * the definition yet). + */ + irb_->SetInsertPoint(placeholder_bb_); + ::llvm::Function::arg_iterator arg_iter(func_->arg_begin()); + arg_iter++; /* Skip path method */ + for (int i = 0; i < mir_graph_->GetNumSSARegs(); i++) { + ::llvm::Value* val; + RegLocation rl_temp = mir_graph_->reg_location_[i]; + if ((mir_graph_->SRegToVReg(i) < 0) || rl_temp.high_word) { + llvm_values_.Insert(0); + } else if ((i < cu_->num_regs) || + (i >= (cu_->num_regs + cu_->num_ins))) { + ::llvm::Constant* imm_value = mir_graph_->reg_location_[i].wide ? + irb_->getJLong(0) : irb_->getJInt(0); + val = EmitConst(imm_value, mir_graph_->reg_location_[i]); + val->setName(mir_graph_->GetSSAString(i)); + llvm_values_.Insert(val); + } else { + // Recover previously-created argument values + ::llvm::Value* arg_val = arg_iter++; + llvm_values_.Insert(arg_val); + } + } + + PreOrderDfsIterator iter2(mir_graph_, false /* not iterative */); + for (BasicBlock* bb = iter2.Next(); bb != NULL; bb = iter2.Next()) { + BlockBitcodeConversion(bb); + } + + /* + * In a few rare cases of verification failure, the verifier will + * replace one or more Dalvik opcodes with the special + * throw-verification-failure opcode. This can leave the SSA graph + * in an invalid state, as definitions may be lost, while uses retained. + * To work around this problem, we insert placeholder definitions for + * all Dalvik SSA regs in the "placeholder" block. Here, after + * bitcode conversion is complete, we examine those placeholder definitions + * and delete any with no references (which normally is all of them). + * + * If any definitions remain, we link the placeholder block into the + * CFG. Otherwise, it is deleted. + */ + for (::llvm::BasicBlock::iterator it = placeholder_bb_->begin(), + it_end = placeholder_bb_->end(); it != it_end;) { + ::llvm::Instruction* inst = ::llvm::dyn_cast< ::llvm::Instruction>(it++); + DCHECK(inst != NULL); + ::llvm::Value* val = ::llvm::dyn_cast< ::llvm::Value>(inst); + DCHECK(val != NULL); + if (val->getNumUses() == 0) { + inst->eraseFromParent(); + } + } + SetDexOffset(0); + if (placeholder_bb_->empty()) { + placeholder_bb_->eraseFromParent(); + } else { + irb_->SetInsertPoint(placeholder_bb_); + irb_->CreateBr(entry_target_bb_); + entry_target_bb_ = placeholder_bb_; + } + irb_->SetInsertPoint(entry_bb_); + irb_->CreateBr(entry_target_bb_); + + if (cu_->enable_debug & (1 << kDebugVerifyBitcode)) { + if (::llvm::verifyFunction(*func_, ::llvm::PrintMessageAction)) { + LOG(INFO) << "Bitcode verification FAILED for " + << PrettyMethod(cu_->method_idx, *cu_->dex_file) + << " of size " << cu_->code_item->insns_size_in_code_units_; + cu_->enable_debug |= (1 << kDebugDumpBitcodeFile); + } + } + + if (cu_->enable_debug & (1 << kDebugDumpBitcodeFile)) { + // Write bitcode to file + std::string errmsg; + std::string fname(PrettyMethod(cu_->method_idx, *cu_->dex_file)); + mir_graph_->ReplaceSpecialChars(fname); + // TODO: make configurable change naming mechanism to avoid fname length issues. + fname = StringPrintf("/sdcard/Bitcode/%s.bc", fname.c_str()); + + if (fname.size() > 240) { + LOG(INFO) << "Warning: bitcode filename too long. Truncated."; + fname.resize(240); + } + + ::llvm::OwningPtr< ::llvm::tool_output_file> out_file( + new ::llvm::tool_output_file(fname.c_str(), errmsg, + ::llvm::raw_fd_ostream::F_Binary)); + + if (!errmsg.empty()) { + LOG(ERROR) << "Failed to create bitcode output file: " << errmsg; + } + + ::llvm::WriteBitcodeToFile(module_, out_file->os()); + out_file->keep(); + } +} + +Backend* PortableCodeGenerator(CompilationUnit* const cu, MIRGraph* const mir_graph, + ArenaAllocator* const arena, + llvm::LlvmCompilationUnit* const llvm_compilation_unit) { + return new MirConverter(cu, mir_graph, arena, llvm_compilation_unit); +} + +} // namespace art diff --git a/src/compiler/dex/portable/mir_to_gbc.h b/src/compiler/dex/portable/mir_to_gbc.h new file mode 100644 index 0000000000000000000000000000000000000000..233735b769f4699288d595c0a8cfd82b17c1f6b1 --- /dev/null +++ b/src/compiler/dex/portable/mir_to_gbc.h @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2011 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_SRC_COMPILER_DEX_PORTABLE_MIRTOGBC_H_ +#define ART_SRC_COMPILER_DEX_PORTABLE_MIRTOGBC_H_ + +#include "invoke_type.h" +#include "compiled_method.h" +#include "compiler/dex/compiler_enums.h" +#include "compiler/dex/compiler_ir.h" +#include "compiler/dex/backend.h" +#include "compiler/llvm/llvm_compilation_unit.h" +#include "safe_map.h" + +namespace art { + +struct BasicBlock; +struct CallInfo; +struct CompilationUnit; +struct MIR; +struct RegLocation; +struct RegisterInfo; +class MIRGraph; + +// Target-specific initialization. +Backend* PortableCodeGenerator(CompilationUnit* const cu, MIRGraph* const mir_graph, + ArenaAllocator* const arena, + llvm::LlvmCompilationUnit* const llvm_compilation_unit); + +class MirConverter : public Backend { + + public: + // TODO: flesh out and integrate into new world order. + MirConverter(CompilationUnit* cu, MIRGraph* mir_graph, ArenaAllocator* arena, + llvm::LlvmCompilationUnit* llvm_compilation_unit) + : Backend(arena), + cu_(cu), + mir_graph_(mir_graph), + llvm_compilation_unit_(llvm_compilation_unit), + llvm_info_(llvm_compilation_unit->GetQuickContext()), + symbol_(llvm_compilation_unit->GetDexCompilationUnit()->GetSymbol()), + context_(NULL), + module_(NULL), + func_(NULL), + intrinsic_helper_(NULL), + irb_(NULL), + placeholder_bb_(NULL), + entry_bb_(NULL), + entry_target_bb_(NULL), + llvm_values_(arena, mir_graph->GetNumSSARegs()), + temp_name_(0), + current_dalvik_offset_(0) { + if (kIsDebugBuild) { + cu->enable_debug |= (1 << kDebugVerifyBitcode); + } + } + + void Materialize() { + MethodMIR2Bitcode(); + } + + CompiledMethod* GetCompiledMethod() { + return NULL; + } + + private: + ::llvm::BasicBlock* GetLLVMBlock(int id); + ::llvm::Value* GetLLVMValue(int s_reg); + void SetVregOnValue(::llvm::Value* val, int s_reg); + void DefineValueOnly(::llvm::Value* val, int s_reg); + void DefineValue(::llvm::Value* val, int s_reg); + ::llvm::Type* LlvmTypeFromLocRec(RegLocation loc); + void InitIR(); + ::llvm::BasicBlock* FindCaseTarget(uint32_t vaddr); + void ConvertPackedSwitch(BasicBlock* bb, int32_t table_offset, + RegLocation rl_src); + void ConvertSparseSwitch(BasicBlock* bb, int32_t table_offset, + RegLocation rl_src); + void ConvertSget(int32_t field_index, + art::llvm::IntrinsicHelper::IntrinsicId id, RegLocation rl_dest); + void ConvertSput(int32_t field_index, + art::llvm::IntrinsicHelper::IntrinsicId id, RegLocation rl_src); + void ConvertFillArrayData(int32_t offset, RegLocation rl_array); + ::llvm::Value* EmitConst(::llvm::ArrayRef< ::llvm::Value*> src, + RegLocation loc); + void EmitPopShadowFrame(); + ::llvm::Value* EmitCopy(::llvm::ArrayRef< ::llvm::Value*> src, + RegLocation loc); + void ConvertMoveException(RegLocation rl_dest); + void ConvertThrow(RegLocation rl_src); + void ConvertMonitorEnterExit(int opt_flags, + art::llvm::IntrinsicHelper::IntrinsicId id, RegLocation rl_src); + void ConvertArrayLength(int opt_flags, RegLocation rl_dest, + RegLocation rl_src); + void EmitSuspendCheck(); + ::llvm::Value* ConvertCompare(ConditionCode cc, + ::llvm::Value* src1, ::llvm::Value* src2); + void ConvertCompareAndBranch(BasicBlock* bb, MIR* mir, ConditionCode cc, + RegLocation rl_src1, RegLocation rl_src2); + void ConvertCompareZeroAndBranch(BasicBlock* bb, MIR* mir, ConditionCode cc, + RegLocation rl_src1); + ::llvm::Value* GenDivModOp(bool is_div, bool is_long, ::llvm::Value* src1, + ::llvm::Value* src2); + ::llvm::Value* GenArithOp(OpKind op, bool is_long, ::llvm::Value* src1, + ::llvm::Value* src2); + void ConvertFPArithOp(OpKind op, RegLocation rl_dest, RegLocation rl_src1, + RegLocation rl_src2); + void ConvertShift(art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_dest, RegLocation rl_src1, RegLocation rl_src2); + void ConvertShiftLit(art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_dest, RegLocation rl_src, int shift_amount); + void ConvertArithOp(OpKind op, RegLocation rl_dest, RegLocation rl_src1, + RegLocation rl_src2); + void ConvertArithOpLit(OpKind op, RegLocation rl_dest, RegLocation rl_src1, + int32_t imm); + void ConvertInvoke(BasicBlock* bb, MIR* mir, InvokeType invoke_type, + bool is_range, bool is_filled_new_array); + void ConvertConstObject(uint32_t idx, + art::llvm::IntrinsicHelper::IntrinsicId id, RegLocation rl_dest); + void ConvertCheckCast(uint32_t type_idx, RegLocation rl_src); + void ConvertNewInstance(uint32_t type_idx, RegLocation rl_dest); + void ConvertNewArray(uint32_t type_idx, RegLocation rl_dest, + RegLocation rl_src); + void ConvertAget(int opt_flags, art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_dest, RegLocation rl_array, RegLocation rl_index); + void ConvertAput(int opt_flags, art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_src, RegLocation rl_array, RegLocation rl_index); + void ConvertIget(int opt_flags, art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_dest, RegLocation rl_obj, int field_index); + void ConvertIput(int opt_flags, art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_src, RegLocation rl_obj, int field_index); + void ConvertInstanceOf(uint32_t type_idx, RegLocation rl_dest, + RegLocation rl_src); + void ConvertIntToLong(RegLocation rl_dest, RegLocation rl_src); + void ConvertLongToInt(RegLocation rl_dest, RegLocation rl_src); + void ConvertFloatToDouble(RegLocation rl_dest, RegLocation rl_src); + void ConvertDoubleToFloat(RegLocation rl_dest, RegLocation rl_src); + void ConvertWideComparison(art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_dest, RegLocation rl_src1, RegLocation rl_src2); + void ConvertIntNarrowing(RegLocation rl_dest, RegLocation rl_src, + art::llvm::IntrinsicHelper::IntrinsicId id); + void ConvertNeg(RegLocation rl_dest, RegLocation rl_src); + void ConvertIntToFP(::llvm::Type* ty, RegLocation rl_dest, RegLocation rl_src); + void ConvertFPToInt(art::llvm::IntrinsicHelper::IntrinsicId id, + RegLocation rl_dest, RegLocation rl_src); + void ConvertNegFP(RegLocation rl_dest, RegLocation rl_src); + void ConvertNot(RegLocation rl_dest, RegLocation rl_src); + void EmitConstructorBarrier(); + bool ConvertMIRNode(MIR* mir, BasicBlock* bb, ::llvm::BasicBlock* llvm_bb); + void SetDexOffset(int32_t offset); + void SetMethodInfo(); + void HandlePhiNodes(BasicBlock* bb, ::llvm::BasicBlock* llvm_bb); + void ConvertExtendedMIR(BasicBlock* bb, MIR* mir, ::llvm::BasicBlock* llvm_bb); + bool BlockBitcodeConversion(BasicBlock* bb); + ::llvm::FunctionType* GetFunctionType(); + bool CreateFunction(); + bool CreateLLVMBasicBlock(BasicBlock* bb); + void MethodMIR2Bitcode(); + + CompilationUnit* cu_; + MIRGraph* mir_graph_; + llvm::LlvmCompilationUnit* const llvm_compilation_unit_; + LLVMInfo* llvm_info_; + std::string symbol_; + ::llvm::LLVMContext* context_; + ::llvm::Module* module_; + ::llvm::Function* func_; + art::llvm::IntrinsicHelper* intrinsic_helper_; + art::llvm::IRBuilder* irb_; + ::llvm::BasicBlock* placeholder_bb_; + ::llvm::BasicBlock* entry_bb_; + ::llvm::BasicBlock* entry_target_bb_; + std::string bitcode_filename_; + GrowableArray< ::llvm::Value*> llvm_values_; + int32_t temp_name_; + SafeMap id_to_block_map_; // block id -> llvm bb. + int current_dalvik_offset_; +}; // Class MirConverter + +} // namespace art + +#endif // ART_SRC_COMPILER_DEX_PORTABLE_MIRTOGBC_H_ diff --git a/src/compiler/dex/quick/arm/arm_lir.h b/src/compiler/dex/quick/arm/arm_lir.h new file mode 100644 index 0000000000000000000000000000000000000000..abcaacc214456d38d9b3ad44cc57fad4a14bfc1d --- /dev/null +++ b/src/compiler/dex/quick/arm/arm_lir.h @@ -0,0 +1,499 @@ +/* + * Copyright (C) 2011 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_SRC_COMPILER_DEX_QUICK_ARM_ARMLIR_H_ +#define ART_SRC_COMPILER_DEX_QUICK_ARM_ARMLIR_H_ + +#include "compiler/dex/compiler_internals.h" + +namespace art { + +/* + * Runtime register usage conventions. + * + * r0-r3: Argument registers in both Dalvik and C/C++ conventions. + * However, for Dalvik->Dalvik calls we'll pass the target's Method* + * pointer in r0 as a hidden arg0. Otherwise used as codegen scratch + * registers. + * r0-r1: As in C/C++ r0 is 32-bit return register and r0/r1 is 64-bit + * r4 : (rARM_SUSPEND) is reserved (suspend check/debugger assist) + * r5 : Callee save (promotion target) + * r6 : Callee save (promotion target) + * r7 : Callee save (promotion target) + * r8 : Callee save (promotion target) + * r9 : (rARM_SELF) is reserved (pointer to thread-local storage) + * r10 : Callee save (promotion target) + * r11 : Callee save (promotion target) + * r12 : Scratch, may be trashed by linkage stubs + * r13 : (sp) is reserved + * r14 : (lr) is reserved + * r15 : (pc) is reserved + * + * 5 core temps that codegen can use (r0, r1, r2, r3, r12) + * 7 core registers that can be used for promotion + * + * Floating pointer registers + * s0-s31 + * d0-d15, where d0={s0,s1}, d1={s2,s3}, ... , d15={s30,s31} + * + * s16-s31 (d8-d15) preserved across C calls + * s0-s15 (d0-d7) trashed across C calls + * + * s0-s15/d0-d7 used as codegen temp/scratch + * s16-s31/d8-d31 can be used for promotion. + * + * Calling convention + * o On a call to a Dalvik method, pass target's Method* in r0 + * o r1-r3 will be used for up to the first 3 words of arguments + * o Arguments past the first 3 words will be placed in appropriate + * out slots by the caller. + * o If a 64-bit argument would span the register/memory argument + * boundary, it will instead be fully passed in the frame. + * o Maintain a 16-byte stack alignment + * + * Stack frame diagram (stack grows down, higher addresses at top): + * + * +------------------------+ + * | IN[ins-1] | {Note: resides in caller's frame} + * | . | + * | IN[0] | + * | caller's Method* | + * +========================+ {Note: start of callee's frame} + * | spill region | {variable sized - will include lr if non-leaf.} + * +------------------------+ + * | ...filler word... | {Note: used as 2nd word of V[locals-1] if long] + * +------------------------+ + * | V[locals-1] | + * | V[locals-2] | + * | . | + * | . | + * | V[1] | + * | V[0] | + * +------------------------+ + * | 0 to 3 words padding | + * +------------------------+ + * | OUT[outs-1] | + * | OUT[outs-2] | + * | . | + * | OUT[0] | + * | cur_method* | <<== sp w/ 16-byte alignment + * +========================+ + */ + +// Offset to distingish FP regs. +#define ARM_FP_REG_OFFSET 32 +// Offset to distinguish DP FP regs. +#define ARM_FP_DOUBLE 64 +// First FP callee save. +#define ARM_FP_CALLEE_SAVE_BASE 16 +// Reg types. +#define ARM_REGTYPE(x) (x & (ARM_FP_REG_OFFSET | ARM_FP_DOUBLE)) +#define ARM_FPREG(x) ((x & ARM_FP_REG_OFFSET) == ARM_FP_REG_OFFSET) +#define ARM_LOWREG(x) ((x & 0x7) == x) +#define ARM_DOUBLEREG(x) ((x & ARM_FP_DOUBLE) == ARM_FP_DOUBLE) +#define ARM_SINGLEREG(x) (ARM_FPREG(x) && !ARM_DOUBLEREG(x)) + +/* + * Note: the low register of a floating point pair is sufficient to + * create the name of a double, but require both names to be passed to + * allow for asserts to verify that the pair is consecutive if significant + * rework is done in this area. Also, it is a good reminder in the calling + * code that reg locations always describe doubles as a pair of singles. + */ +#define ARM_S2D(x,y) ((x) | ARM_FP_DOUBLE) +// Mask to strip off fp flags. +#define ARM_FP_REG_MASK (ARM_FP_REG_OFFSET-1) + +// RegisterLocation templates return values (r0, or r0/r1). +#define ARM_LOC_C_RETURN {kLocPhysReg, 0, 0, 0, 0, 0, 0, 0, 1, r0, INVALID_REG,\ + INVALID_SREG, INVALID_SREG} +#define ARM_LOC_C_RETURN_WIDE {kLocPhysReg, 1, 0, 0, 0, 0, 0, 0, 1, r0, r1, \ + INVALID_SREG, INVALID_SREG} +#define ARM_LOC_C_RETURN_FLOAT ARM_LOC_C_RETURN +#define ARM_LOC_C_RETURN_DOUBLE ARM_LOC_C_RETURN_WIDE + +enum ArmResourceEncodingPos { + kArmGPReg0 = 0, + kArmRegSP = 13, + kArmRegLR = 14, + kArmRegPC = 15, + kArmFPReg0 = 16, + kArmFPReg16 = 32, + kArmRegEnd = 48, +}; + +#define ENCODE_ARM_REG_LIST(N) (static_cast(N)) +#define ENCODE_ARM_REG_SP (1ULL << kArmRegSP) +#define ENCODE_ARM_REG_LR (1ULL << kArmRegLR) +#define ENCODE_ARM_REG_PC (1ULL << kArmRegPC) +#define ENCODE_ARM_REG_FPCS_LIST(N) (static_cast(N) << kArmFPReg16) + +enum ArmNativeRegisterPool { + r0 = 0, + r1 = 1, + r2 = 2, + r3 = 3, + rARM_SUSPEND = 4, + r5 = 5, + r6 = 6, + r7 = 7, + r8 = 8, + rARM_SELF = 9, + r10 = 10, + r11 = 11, + r12 = 12, + r13sp = 13, + rARM_SP = 13, + r14lr = 14, + rARM_LR = 14, + r15pc = 15, + rARM_PC = 15, + fr0 = 0 + ARM_FP_REG_OFFSET, + fr1 = 1 + ARM_FP_REG_OFFSET, + fr2 = 2 + ARM_FP_REG_OFFSET, + fr3 = 3 + ARM_FP_REG_OFFSET, + fr4 = 4 + ARM_FP_REG_OFFSET, + fr5 = 5 + ARM_FP_REG_OFFSET, + fr6 = 6 + ARM_FP_REG_OFFSET, + fr7 = 7 + ARM_FP_REG_OFFSET, + fr8 = 8 + ARM_FP_REG_OFFSET, + fr9 = 9 + ARM_FP_REG_OFFSET, + fr10 = 10 + ARM_FP_REG_OFFSET, + fr11 = 11 + ARM_FP_REG_OFFSET, + fr12 = 12 + ARM_FP_REG_OFFSET, + fr13 = 13 + ARM_FP_REG_OFFSET, + fr14 = 14 + ARM_FP_REG_OFFSET, + fr15 = 15 + ARM_FP_REG_OFFSET, + fr16 = 16 + ARM_FP_REG_OFFSET, + fr17 = 17 + ARM_FP_REG_OFFSET, + fr18 = 18 + ARM_FP_REG_OFFSET, + fr19 = 19 + ARM_FP_REG_OFFSET, + fr20 = 20 + ARM_FP_REG_OFFSET, + fr21 = 21 + ARM_FP_REG_OFFSET, + fr22 = 22 + ARM_FP_REG_OFFSET, + fr23 = 23 + ARM_FP_REG_OFFSET, + fr24 = 24 + ARM_FP_REG_OFFSET, + fr25 = 25 + ARM_FP_REG_OFFSET, + fr26 = 26 + ARM_FP_REG_OFFSET, + fr27 = 27 + ARM_FP_REG_OFFSET, + fr28 = 28 + ARM_FP_REG_OFFSET, + fr29 = 29 + ARM_FP_REG_OFFSET, + fr30 = 30 + ARM_FP_REG_OFFSET, + fr31 = 31 + ARM_FP_REG_OFFSET, + dr0 = fr0 + ARM_FP_DOUBLE, + dr1 = fr2 + ARM_FP_DOUBLE, + dr2 = fr4 + ARM_FP_DOUBLE, + dr3 = fr6 + ARM_FP_DOUBLE, + dr4 = fr8 + ARM_FP_DOUBLE, + dr5 = fr10 + ARM_FP_DOUBLE, + dr6 = fr12 + ARM_FP_DOUBLE, + dr7 = fr14 + ARM_FP_DOUBLE, + dr8 = fr16 + ARM_FP_DOUBLE, + dr9 = fr18 + ARM_FP_DOUBLE, + dr10 = fr20 + ARM_FP_DOUBLE, + dr11 = fr22 + ARM_FP_DOUBLE, + dr12 = fr24 + ARM_FP_DOUBLE, + dr13 = fr26 + ARM_FP_DOUBLE, + dr14 = fr28 + ARM_FP_DOUBLE, + dr15 = fr30 + ARM_FP_DOUBLE, +}; + +// Target-independent aliases. +#define rARM_ARG0 r0 +#define rARM_ARG1 r1 +#define rARM_ARG2 r2 +#define rARM_ARG3 r3 +#define rARM_FARG0 r0 +#define rARM_FARG1 r1 +#define rARM_FARG2 r2 +#define rARM_FARG3 r3 +#define rARM_RET0 r0 +#define rARM_RET1 r1 +#define rARM_INVOKE_TGT rARM_LR +#define rARM_COUNT INVALID_REG + +enum ArmShiftEncodings { + kArmLsl = 0x0, + kArmLsr = 0x1, + kArmAsr = 0x2, + kArmRor = 0x3 +}; + +/* + * The following enum defines the list of supported Thumb instructions by the + * assembler. Their corresponding EncodingMap positions will be defined in + * Assemble.cc. + */ +enum ArmOpcode { + kArmFirst = 0, + kArm16BitData = kArmFirst, // DATA [0] rd[15..0]. + kThumbAdcRR, // adc [0100000101] rm[5..3] rd[2..0]. + kThumbAddRRI3, // add(1) [0001110] imm_3[8..6] rn[5..3] rd[2..0]*/ + kThumbAddRI8, // add(2) [00110] rd[10..8] imm_8[7..0]. + kThumbAddRRR, // add(3) [0001100] rm[8..6] rn[5..3] rd[2..0]. + kThumbAddRRLH, // add(4) [01000100] H12[01] rm[5..3] rd[2..0]. + kThumbAddRRHL, // add(4) [01001000] H12[10] rm[5..3] rd[2..0]. + kThumbAddRRHH, // add(4) [01001100] H12[11] rm[5..3] rd[2..0]. + kThumbAddPcRel, // add(5) [10100] rd[10..8] imm_8[7..0]. + kThumbAddSpRel, // add(6) [10101] rd[10..8] imm_8[7..0]. + kThumbAddSpI7, // add(7) [101100000] imm_7[6..0]. + kThumbAndRR, // and [0100000000] rm[5..3] rd[2..0]. + kThumbAsrRRI5, // asr(1) [00010] imm_5[10..6] rm[5..3] rd[2..0]. + kThumbAsrRR, // asr(2) [0100000100] rs[5..3] rd[2..0]. + kThumbBCond, // b(1) [1101] cond[11..8] offset_8[7..0]. + kThumbBUncond, // b(2) [11100] offset_11[10..0]. + kThumbBicRR, // bic [0100001110] rm[5..3] rd[2..0]. + kThumbBkpt, // bkpt [10111110] imm_8[7..0]. + kThumbBlx1, // blx(1) [111] H[10] offset_11[10..0]. + kThumbBlx2, // blx(1) [111] H[01] offset_11[10..0]. + kThumbBl1, // blx(1) [111] H[10] offset_11[10..0]. + kThumbBl2, // blx(1) [111] H[11] offset_11[10..0]. + kThumbBlxR, // blx(2) [010001111] rm[6..3] [000]. + kThumbBx, // bx [010001110] H2[6..6] rm[5..3] SBZ[000]. + kThumbCmnRR, // cmn [0100001011] rm[5..3] rd[2..0]. + kThumbCmpRI8, // cmp(1) [00101] rn[10..8] imm_8[7..0]. + kThumbCmpRR, // cmp(2) [0100001010] rm[5..3] rd[2..0]. + kThumbCmpLH, // cmp(3) [01000101] H12[01] rm[5..3] rd[2..0]. + kThumbCmpHL, // cmp(3) [01000110] H12[10] rm[5..3] rd[2..0]. + kThumbCmpHH, // cmp(3) [01000111] H12[11] rm[5..3] rd[2..0]. + kThumbEorRR, // eor [0100000001] rm[5..3] rd[2..0]. + kThumbLdmia, // ldmia [11001] rn[10..8] reglist [7..0]. + kThumbLdrRRI5, // ldr(1) [01101] imm_5[10..6] rn[5..3] rd[2..0]. + kThumbLdrRRR, // ldr(2) [0101100] rm[8..6] rn[5..3] rd[2..0]. + kThumbLdrPcRel, // ldr(3) [01001] rd[10..8] imm_8[7..0]. + kThumbLdrSpRel, // ldr(4) [10011] rd[10..8] imm_8[7..0]. + kThumbLdrbRRI5, // ldrb(1) [01111] imm_5[10..6] rn[5..3] rd[2..0]. + kThumbLdrbRRR, // ldrb(2) [0101110] rm[8..6] rn[5..3] rd[2..0]. + kThumbLdrhRRI5, // ldrh(1) [10001] imm_5[10..6] rn[5..3] rd[2..0]. + kThumbLdrhRRR, // ldrh(2) [0101101] rm[8..6] rn[5..3] rd[2..0]. + kThumbLdrsbRRR, // ldrsb [0101011] rm[8..6] rn[5..3] rd[2..0]. + kThumbLdrshRRR, // ldrsh [0101111] rm[8..6] rn[5..3] rd[2..0]. + kThumbLslRRI5, // lsl(1) [00000] imm_5[10..6] rm[5..3] rd[2..0]. + kThumbLslRR, // lsl(2) [0100000010] rs[5..3] rd[2..0]. + kThumbLsrRRI5, // lsr(1) [00001] imm_5[10..6] rm[5..3] rd[2..0]. + kThumbLsrRR, // lsr(2) [0100000011] rs[5..3] rd[2..0]. + kThumbMovImm, // mov(1) [00100] rd[10..8] imm_8[7..0]. + kThumbMovRR, // mov(2) [0001110000] rn[5..3] rd[2..0]. + kThumbMovRR_H2H, // mov(3) [01000111] H12[11] rm[5..3] rd[2..0]. + kThumbMovRR_H2L, // mov(3) [01000110] H12[01] rm[5..3] rd[2..0]. + kThumbMovRR_L2H, // mov(3) [01000101] H12[10] rm[5..3] rd[2..0]. + kThumbMul, // mul [0100001101] rm[5..3] rd[2..0]. + kThumbMvn, // mvn [0100001111] rm[5..3] rd[2..0]. + kThumbNeg, // neg [0100001001] rm[5..3] rd[2..0]. + kThumbOrr, // orr [0100001100] rm[5..3] rd[2..0]. + kThumbPop, // pop [1011110] r[8..8] rl[7..0]. + kThumbPush, // push [1011010] r[8..8] rl[7..0]. + kThumbRorRR, // ror [0100000111] rs[5..3] rd[2..0]. + kThumbSbc, // sbc [0100000110] rm[5..3] rd[2..0]. + kThumbStmia, // stmia [11000] rn[10..8] reglist [7.. 0]. + kThumbStrRRI5, // str(1) [01100] imm_5[10..6] rn[5..3] rd[2..0]. + kThumbStrRRR, // str(2) [0101000] rm[8..6] rn[5..3] rd[2..0]. + kThumbStrSpRel, // str(3) [10010] rd[10..8] imm_8[7..0]. + kThumbStrbRRI5, // strb(1) [01110] imm_5[10..6] rn[5..3] rd[2..0]. + kThumbStrbRRR, // strb(2) [0101010] rm[8..6] rn[5..3] rd[2..0]. + kThumbStrhRRI5, // strh(1) [10000] imm_5[10..6] rn[5..3] rd[2..0]. + kThumbStrhRRR, // strh(2) [0101001] rm[8..6] rn[5..3] rd[2..0]. + kThumbSubRRI3, // sub(1) [0001111] imm_3[8..6] rn[5..3] rd[2..0]*/ + kThumbSubRI8, // sub(2) [00111] rd[10..8] imm_8[7..0]. + kThumbSubRRR, // sub(3) [0001101] rm[8..6] rn[5..3] rd[2..0]. + kThumbSubSpI7, // sub(4) [101100001] imm_7[6..0]. + kThumbSwi, // swi [11011111] imm_8[7..0]. + kThumbTst, // tst [0100001000] rm[5..3] rn[2..0]. + kThumb2Vldrs, // vldr low sx [111011011001] rn[19..16] rd[15-12] [1010] imm_8[7..0]. + kThumb2Vldrd, // vldr low dx [111011011001] rn[19..16] rd[15-12] [1011] imm_8[7..0]. + kThumb2Vmuls, // vmul vd, vn, vm [111011100010] rn[19..16] rd[15-12] [10100000] rm[3..0]. + kThumb2Vmuld, // vmul vd, vn, vm [111011100010] rn[19..16] rd[15-12] [10110000] rm[3..0]. + kThumb2Vstrs, // vstr low sx [111011011000] rn[19..16] rd[15-12] [1010] imm_8[7..0]. + kThumb2Vstrd, // vstr low dx [111011011000] rn[19..16] rd[15-12] [1011] imm_8[7..0]. + kThumb2Vsubs, // vsub vd, vn, vm [111011100011] rn[19..16] rd[15-12] [10100040] rm[3..0]. + kThumb2Vsubd, // vsub vd, vn, vm [111011100011] rn[19..16] rd[15-12] [10110040] rm[3..0]. + kThumb2Vadds, // vadd vd, vn, vm [111011100011] rn[19..16] rd[15-12] [10100000] rm[3..0]. + kThumb2Vaddd, // vadd vd, vn, vm [111011100011] rn[19..16] rd[15-12] [10110000] rm[3..0]. + kThumb2Vdivs, // vdiv vd, vn, vm [111011101000] rn[19..16] rd[15-12] [10100000] rm[3..0]. + kThumb2Vdivd, // vdiv vd, vn, vm [111011101000] rn[19..16] rd[15-12] [10110000] rm[3..0]. + kThumb2VcvtIF, // vcvt.F32 vd, vm [1110111010111000] vd[15..12] [10101100] vm[3..0]. + kThumb2VcvtID, // vcvt.F64 vd, vm [1110111010111000] vd[15..12] [10111100] vm[3..0]. + kThumb2VcvtFI, // vcvt.S32.F32 vd, vm [1110111010111101] vd[15..12] [10101100] vm[3..0]. + kThumb2VcvtDI, // vcvt.S32.F32 vd, vm [1110111010111101] vd[15..12] [10111100] vm[3..0]. + kThumb2VcvtFd, // vcvt.F64.F32 vd, vm [1110111010110111] vd[15..12] [10101100] vm[3..0]. + kThumb2VcvtDF, // vcvt.F32.F64 vd, vm [1110111010110111] vd[15..12] [10111100] vm[3..0]. + kThumb2Vsqrts, // vsqrt.f32 vd, vm [1110111010110001] vd[15..12] [10101100] vm[3..0]. + kThumb2Vsqrtd, // vsqrt.f64 vd, vm [1110111010110001] vd[15..12] [10111100] vm[3..0]. + kThumb2MovImmShift,// mov(T2) rd, # [11110] i [00001001111] imm3 rd[11..8] imm8. + kThumb2MovImm16, // mov(T3) rd, # [11110] i [0010100] imm4 [0] imm3 rd[11..8] imm8. + kThumb2StrRRI12, // str(Imm,T3) rd,[rn,#imm12] [111110001100] rn[19..16] rt[15..12] imm12[11..0]. + kThumb2LdrRRI12, // str(Imm,T3) rd,[rn,#imm12] [111110001100] rn[19..16] rt[15..12] imm12[11..0]. + kThumb2StrRRI8Predec, // str(Imm,T4) rd,[rn,#-imm8] [111110000100] rn[19..16] rt[15..12] [1100] imm[7..0]*/ + kThumb2LdrRRI8Predec, // ldr(Imm,T4) rd,[rn,#-imm8] [111110000101] rn[19..16] rt[15..12] [1100] imm[7..0]*/ + kThumb2Cbnz, // cbnz rd,