diff --git a/Android.mk b/Android.mk index 46a7c1ec3e2..8c78580949f 100644 --- a/Android.mk +++ b/Android.mk @@ -17,12 +17,13 @@ LOCAL_PATH := $(call my-dir) art_path := $(LOCAL_PATH) -art_build_path := $(art_path)/build ######################################################################## -# clean-oat targets +# clean-oat rules # +include $(art_path)/build/Android.common_path.mk + # following the example of build's dont_bother for clean targets ifneq (,$(filter clean-oat,$(MAKECMDGOALS))) art_dont_bother := true @@ -39,214 +40,264 @@ clean-oat: clean-oat-host clean-oat-target .PHONY: clean-oat-host clean-oat-host: - rm -f $(ART_NATIVETEST_OUT)/*.odex - rm -f $(ART_NATIVETEST_OUT)/*.oat - rm -f $(ART_NATIVETEST_OUT)/*.art - rm -f $(ART_TEST_OUT)/*.odex - rm -f $(ART_TEST_OUT)/*.oat - rm -f $(ART_TEST_OUT)/*.art - rm -f $(DALVIK_CACHE_OUT)/*@classes.dex - rm -f $(DALVIK_CACHE_OUT)/*.oat - rm -f $(DALVIK_CACHE_OUT)/*.art - rm -f $(HOST_OUT_JAVA_LIBRARIES)/*.odex - rm -f $(HOST_OUT_JAVA_LIBRARIES)/*.oat - rm -f $(HOST_OUT_JAVA_LIBRARIES)/*.art - rm -f $(TARGET_OUT_JAVA_LIBRARIES)/*.odex - rm -f $(TARGET_OUT_JAVA_LIBRARIES)/*.oat - rm -f $(TARGET_OUT_JAVA_LIBRARIES)/*.art + rm -f $(HOST_CORE_IMG_OUT) + rm -f $(HOST_CORE_OAT_OUT) + rm -f $(HOST_OUT_JAVA_LIBRARIES)/$(ART_HOST_ARCH)/*.odex +ifneq ($(HOST_PREFER_32_BIT),true) + rm -f $(2ND_HOST_CORE_IMG_OUT) + rm -f $(2ND_HOST_CORE_OAT_OUT) + rm -f $(HOST_OUT_JAVA_LIBRARIES)/$(2ND_ART_HOST_ARCH)/*.odex +endif + rm -f $(TARGET_CORE_IMG_OUT) + rm -f $(TARGET_CORE_OAT_OUT) +ifdef TARGET_2ND_ARCH + rm -f $(2ND_TARGET_CORE_IMG_OUT) + rm -f $(2ND_TARGET_CORE_OAT_OUT) +endif + rm -rf $(DEXPREOPT_PRODUCT_DIR_FULL_PATH) rm -f $(TARGET_OUT_UNSTRIPPED)/system/framework/*.odex - rm -f $(TARGET_OUT_UNSTRIPPED)/system/framework/*.oat + rm -f $(TARGET_OUT_UNSTRIPPED)/system/framework/*/*.oat + rm -f $(TARGET_OUT_UNSTRIPPED)/system/framework/*/*.art + rm -f $(TARGET_OUT)/framework/*/*.oat + rm -f $(TARGET_OUT)/framework/*/*.art rm -f $(TARGET_OUT_APPS)/*.odex rm -f $(TARGET_OUT_INTERMEDIATES)/JAVA_LIBRARIES/*_intermediates/javalib.odex rm -f $(TARGET_OUT_INTERMEDIATES)/APPS/*_intermediates/*.odex - rm -rf /tmp/test-*/dalvik-cache/*@classes.dex +ifdef TARGET_2ND_ARCH + rm -f $(2ND_TARGET_OUT_INTERMEDIATES)/JAVA_LIBRARIES/*_intermediates/javalib.odex + rm -f $(2ND_TARGET_OUT_INTERMEDIATES)/APPS/*_intermediates/*.odex +endif +ifneq ($(TMPDIR),) + rm -rf $(TMPDIR)/$(USER)/test-*/dalvik-cache/* + rm -rf $(TMPDIR)/android-data/dalvik-cache/* +else + rm -rf /tmp/$(USER)/test-*/dalvik-cache/* + rm -rf /tmp/android-data/dalvik-cache/* +endif .PHONY: clean-oat-target clean-oat-target: adb remount - adb shell rm $(ART_NATIVETEST_DIR)/*.odex - adb shell rm $(ART_NATIVETEST_DIR)/*.oat - adb shell rm $(ART_NATIVETEST_DIR)/*.art - adb shell rm $(ART_TEST_DIR)/*.odex - adb shell rm $(ART_TEST_DIR)/*.oat - adb shell rm $(ART_TEST_DIR)/*.art - adb shell rm $(DALVIK_CACHE_DIR)/*.dex - adb shell rm $(DALVIK_CACHE_DIR)/*.oat - adb shell rm $(DALVIK_CACHE_DIR)/*.art - adb shell rm $(DEXPREOPT_BOOT_JAR_DIR)/*.oat - adb shell rm $(DEXPREOPT_BOOT_JAR_DIR)/*.art - adb shell rm system/app/*.odex - adb shell rm data/run-test/test-*/dalvik-cache/*@classes.dex + adb shell rm -rf $(ART_TARGET_NATIVETEST_DIR) + adb shell rm -rf $(ART_TARGET_TEST_DIR) + adb shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/*/* + adb shell rm -rf $(DEXPREOPT_BOOT_JAR_DIR)/$(DEX2OAT_TARGET_ARCH) + adb shell rm -rf system/app/$(DEX2OAT_TARGET_ARCH) +ifdef TARGET_2ND_ARCH + adb shell rm -rf $(DEXPREOPT_BOOT_JAR_DIR)/$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_ARCH) + adb shell rm -rf system/app/$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_ARCH) +endif + adb shell rm -rf data/run-test/test-*/dalvik-cache/* ifneq ($(art_dont_bother),true) ######################################################################## -# product targets +# cpplint rules to style check art source files + +include $(art_path)/build/Android.cpplint.mk + +######################################################################## +# product rules + include $(art_path)/runtime/Android.mk include $(art_path)/compiler/Android.mk include $(art_path)/dex2oat/Android.mk +include $(art_path)/disassembler/Android.mk include $(art_path)/oatdump/Android.mk +include $(art_path)/patchoat/Android.mk include $(art_path)/dalvikvm/Android.mk -include $(art_path)/jdwpspy/Android.mk -include $(art_build_path)/Android.oat.mk +include $(art_path)/tools/Android.mk +include $(art_path)/build/Android.oat.mk +include $(art_path)/sigchainlib/Android.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 +ART_HOST_DEPENDENCIES := \ + $(ART_HOST_EXECUTABLES) \ + $(HOST_OUT_JAVA_LIBRARIES)/core-libart-hostdex.jar \ + $(ART_HOST_OUT_SHARED_LIBRARIES)/libjavacore$(ART_HOST_SHLIB_EXTENSION) +ART_TARGET_DEPENDENCIES := \ + $(ART_TARGET_EXECUTABLES) \ + $(TARGET_OUT_JAVA_LIBRARIES)/core-libart.jar \ + $(TARGET_OUT_SHARED_LIBRARIES)/libjavacore.so +ifdef TARGET_2ND_ARCH +ART_TARGET_DEPENDENCIES += $(2ND_TARGET_OUT_SHARED_LIBRARIES)/libjavacore.so +endif + +ifdef DISABLED_FOR_XPOSED ######################################################################## -# test targets +# test rules -include $(art_path)/test/Android.mk -include $(art_build_path)/Android.gtest.mk +# All the dependencies that must be built ahead of sync-ing them onto the target device. +TEST_ART_TARGET_SYNC_DEPS := -# 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 $(art_path)/build/Android.common_test.mk +include $(art_path)/build/Android.gtest.mk +include $(art_path)/test/Android.run-test.mk -include $(art_build_path)/Android.libarttest.mk +# Sync test files to the target, depends upon all things that must be pushed to the target. +.PHONY: test-art-target-sync +test-art-target-sync: $(TEST_ART_TARGET_SYNC_DEPS) + adb remount + adb sync + +# Undefine variable now its served its purpose. +TEST_ART_TARGET_SYNC_DEPS := # "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 + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) .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 - @echo test-art-oat PASSED + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) .PHONY: test-art-run-test test-art-run-test: test-art-host-run-test test-art-target-run-test - @echo test-art-run-test PASSED + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) ######################################################################## -# host test targets +# host test rules -# "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 - @echo test-art-host PASSED - -.PHONY: test-art-host-interpreter -test-art-host-interpreter: test-art-host-oat-interpreter test-art-host-run-test-interpreter - @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-default -test-art-host-oat-default: $(ART_TEST_HOST_OAT_DEFAULT_TARGETS) - @echo test-art-host-oat-default PASSED - -.PHONY: test-art-host-oat-interpreter -test-art-host-oat-interpreter: $(ART_TEST_HOST_OAT_INTERPRETER_TARGETS) - @echo test-art-host-oat-interpreter PASSED - -.PHONY: test-art-host-oat -test-art-host-oat: test-art-host-oat-default test-art-host-oat-interpreter - @echo test-art-host-oat PASSED - -define declare-test-art-host-run-test -.PHONY: test-art-host-run-test-default-$(1) -test-art-host-run-test-default-$(1): test-art-host-dependencies - art/test/run-test --host $(1) - @echo test-art-host-run-test-default-$(1) PASSED - -TEST_ART_HOST_RUN_TEST_DEFAULT_TARGETS += test-art-host-run-test-default-$(1) - -.PHONY: test-art-host-run-test-interpreter-$(1) -test-art-host-run-test-interpreter-$(1): test-art-host-dependencies - art/test/run-test --host --interpreter $(1) - @echo test-art-host-run-test-interpreter-$(1) PASSED - -TEST_ART_HOST_RUN_TEST_INTERPRETER_TARGETS += test-art-host-run-test-interpreter-$(1) +VIXL_TEST_DEPENDENCY := +# We can only run the vixl tests on 64-bit hosts (vixl testing issue) when its a +# top-level build (to declare the vixl test rule). +ifneq ($(HOST_PREFER_32_BIT),true) +ifeq ($(ONE_SHOT_MAKEFILE),) +VIXL_TEST_DEPENDENCY := run-vixl-tests +endif +endif -.PHONY: test-art-host-run-test-$(1) -test-art-host-run-test-$(1): test-art-host-run-test-default-$(1) test-art-host-run-test-interpreter-$(1) +.PHONY: test-art-host-vixl +test-art-host-vixl: $(VIXL_TEST_DEPENDENCY) -endef - -$(foreach test, $(wildcard art/test/[0-9]*), $(eval $(call declare-test-art-host-run-test,$(notdir $(test))))) +# "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-run-test test-art-host-vixl + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) -.PHONY: test-art-host-run-test-default -test-art-host-run-test-default: $(TEST_ART_HOST_RUN_TEST_DEFAULT_TARGETS) - @echo test-art-host-run-test-default PASSED +# All host tests that run solely with the default compiler. +.PHONY: test-art-host-default +test-art-host-default: test-art-host-run-test-default + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) -.PHONY: test-art-host-run-test-interpreter -test-art-host-run-test-interpreter: $(TEST_ART_HOST_RUN_TEST_INTERPRETER_TARGETS) - @echo test-art-host-run-test-interpreter PASSED +# All host tests that run solely with the optimizing compiler. +.PHONY: test-art-host-optimizing +test-art-host-optimizing: test-art-host-run-test-optimizing + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) -.PHONY: test-art-host-run-test -test-art-host-run-test: test-art-host-run-test-default test-art-host-run-test-interpreter - @echo test-art-host-run-test PASSED +# All host tests that run solely on the interpreter. +.PHONY: test-art-host-interpreter +test-art-host-interpreter: test-art-host-run-test-interpreter + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +# Primary host architecture variants: +.PHONY: test-art-host$(ART_PHONY_TEST_HOST_SUFFIX) +test-art-host$(ART_PHONY_TEST_HOST_SUFFIX): test-art-host-gtest$(ART_PHONY_TEST_HOST_SUFFIX) \ + test-art-host-run-test$(ART_PHONY_TEST_HOST_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +.PHONY: test-art-host-default$(ART_PHONY_TEST_HOST_SUFFIX) +test-art-host-default$(ART_PHONY_TEST_HOST_SUFFIX): test-art-host-run-test-default$(ART_PHONY_TEST_HOST_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +.PHONY: test-art-host-optimizing$(ART_PHONY_TEST_HOST_SUFFIX) +test-art-host-optimizing$(ART_PHONY_TEST_HOST_SUFFIX): test-art-host-run-test-optimizing$(ART_PHONY_TEST_HOST_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +.PHONY: test-art-host-interpreter$(ART_PHONY_TEST_HOST_SUFFIX) +test-art-host-interpreter$(ART_PHONY_TEST_HOST_SUFFIX): test-art-host-run-test-interpreter$(ART_PHONY_TEST_HOST_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +# Secondary host architecture variants: +ifneq ($(HOST_PREFER_32_BIT),true) +.PHONY: test-art-host$(2ND_ART_PHONY_TEST_HOST_SUFFIX) +test-art-host$(2ND_ART_PHONY_TEST_HOST_SUFFIX): test-art-host-gtest$(2ND_ART_PHONY_TEST_HOST_SUFFIX) \ + test-art-host-run-test$(2ND_ART_PHONY_TEST_HOST_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +.PHONY: test-art-host-default$(2ND_ART_PHONY_TEST_HOST_SUFFIX) +test-art-host-default$(2ND_ART_PHONY_TEST_HOST_SUFFIX): test-art-host-run-test-default$(2ND_ART_PHONY_TEST_HOST_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +.PHONY: test-art-host-optimizing$(2ND_ART_PHONY_TEST_HOST_SUFFIX) +test-art-host-optimizing$(2ND_ART_PHONY_TEST_HOST_SUFFIX): test-art-host-run-test-optimizing$(2ND_ART_PHONY_TEST_HOST_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +.PHONY: test-art-host-interpreter$(2ND_ART_PHONY_TEST_HOST_SUFFIX) +test-art-host-interpreter$(2ND_ART_PHONY_TEST_HOST_SUFFIX): test-art-host-run-test-interpreter$(2ND_ART_PHONY_TEST_HOST_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) +endif ######################################################################## -# target test targets +# target test rules -# "mm test-art-target" to build and run all target tests +# "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) - -test-art-run-test-$(1): test-art-host-run-test-$(1) test-art-target-run-test-$(1) - -endef - -$(foreach test, $(wildcard art/test/[0-9]*), $(eval $(call declare-test-art-target-run-test,$(notdir $(test))))) +test-art-target: test-art-target-gtest test-art-target-run-test + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +# All target tests that run solely with the default compiler. +.PHONY: test-art-target-default +test-art-target-default: test-art-target-run-test-default + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +# All target tests that run solely with the optimizing compiler. +.PHONY: test-art-target-optimizing +test-art-target-optimizing: test-art-target-run-test-optimizing + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +# All target tests that run solely on the interpreter. +.PHONY: test-art-target-interpreter +test-art-target-interpreter: test-art-target-run-test-interpreter + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +# Primary target architecture variants: +.PHONY: test-art-target$(ART_PHONY_TEST_TARGET_SUFFIX) +test-art-target$(ART_PHONY_TEST_TARGET_SUFFIX): test-art-target-gtest$(ART_PHONY_TEST_TARGET_SUFFIX) \ + test-art-target-run-test$(ART_PHONY_TEST_TARGET_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +.PHONY: test-art-target-default$(ART_PHONY_TEST_TARGET_SUFFIX) +test-art-target-default$(ART_PHONY_TEST_TARGET_SUFFIX): test-art-target-run-test-default$(ART_PHONY_TEST_TARGET_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +.PHONY: test-art-target-optimizing$(ART_PHONY_TEST_TARGET_SUFFIX) +test-art-target-optimizing$(ART_PHONY_TEST_TARGET_SUFFIX): test-art-target-run-test-optimizing$(ART_PHONY_TEST_TARGET_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +.PHONY: test-art-target-interpreter$(ART_PHONY_TEST_TARGET_SUFFIX) +test-art-target-interpreter$(ART_PHONY_TEST_TARGET_SUFFIX): test-art-target-run-test-interpreter$(ART_PHONY_TEST_TARGET_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +# Secondary target architecture variants: +ifdef TARGET_2ND_ARCH +.PHONY: test-art-target$(2ND_ART_PHONY_TEST_TARGET_SUFFIX) +test-art-target$(2ND_ART_PHONY_TEST_TARGET_SUFFIX): test-art-target-gtest$(2ND_ART_PHONY_TEST_TARGET_SUFFIX) \ + test-art-target-run-test$(2ND_ART_PHONY_TEST_TARGET_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +.PHONY: test-art-target-default$(2ND_ART_PHONY_TEST_TARGET_SUFFIX) +test-art-target-default$(2ND_ART_PHONY_TEST_TARGET_SUFFIX): test-art-target-run-test-default$(2ND_ART_PHONY_TEST_TARGET_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +.PHONY: test-art-target-optimizing$(2ND_ART_PHONY_TEST_TARGET_SUFFIX) +test-art-target-optimizing$(2ND_ART_PHONY_TEST_TARGET_SUFFIX): test-art-target-run-test-optimizing$(2ND_ART_PHONY_TEST_TARGET_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) + +.PHONY: test-art-target-interpreter$(2ND_ART_PHONY_TEST_TARGET_SUFFIX) +test-art-target-interpreter$(2ND_ART_PHONY_TEST_TARGET_SUFFIX): test-art-target-run-test-interpreter$(2ND_ART_PHONY_TEST_TARGET_SUFFIX) + $(hide) $(call ART_TEST_PREREQ_FINISHED,$@) +endif -.PHONY: test-art-target-run-test -test-art-target-run-test: $(TEST_ART_TARGET_RUN_TEST_TARGETS) - @echo test-art-target-run-test PASSED +endif # DISABLED_FOR_XPOSED ######################################################################## -# oat-target and oat-target-sync targets +# oat-target and oat-target-sync rules -OAT_TARGET_TARGETS := +OAT_TARGET_RULES := # $(1): input jar or apk target location define declare-oat-target-target @@ -267,23 +318,29 @@ else .PHONY: oat-target-$(1) oat-target-$(1): $$(OUT_OAT_FILE) -$$(OUT_OAT_FILE): $(PRODUCT_OUT)/$(1) $(TARGET_BOOT_IMG_OUT) $(DEX2OAT_DEPENDENCY) +$$(OUT_OAT_FILE): $(PRODUCT_OUT)/$(1) $(DEFAULT_DEX_PREOPT_BUILT_IMAGE) $(DEX2OATD_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 + $(DEX2OATD) --runtime-arg -Xms$(DEX2OAT_XMS) --runtime-arg -Xmx$(DEX2OAT_XMX) \ + --boot-image=$(DEFAULT_DEX_PREOPT_BUILT_IMAGE) --dex-file=$(PRODUCT_OUT)/$(1) \ + --dex-location=/$(1) --oat-file=$$@ \ + --instruction-set=$(DEX2OAT_TARGET_ARCH) \ + --instruction-set-features=$(DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) \ + --android-root=$(PRODUCT_OUT)/system --include-patch-information \ + --runtime-arg -Xnorelocate endif -OAT_TARGET_TARGETS += oat-target-$(1) +OAT_TARGET_RULES += oat-target-$(1) endef $(foreach file,\ $(filter-out\ - $(addprefix $(TARGET_OUT_JAVA_LIBRARIES)/,$(addsuffix .jar,$(TARGET_BOOT_JARS))),\ + $(addprefix $(TARGET_OUT_JAVA_LIBRARIES)/,$(addsuffix .jar,$(LIBART_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) +oat-target: $(ART_TARGET_DEPENDENCIES) $(DEFAULT_DEX_PREOPT_INSTALLED_IMAGE) $(OAT_TARGET_RULES) .PHONY: oat-target-sync oat-target-sync: oat-target @@ -296,52 +353,22 @@ oat-target-sync: oat-target build-art: build-art-host build-art-target .PHONY: build-art-host -build-art-host: $(ART_HOST_EXECUTABLES) $(ART_HOST_TEST_EXECUTABLES) $(HOST_CORE_IMG_OUT) $(HOST_OUT)/lib/libjavacore.so +build-art-host: $(ART_HOST_EXECUTABLES) $(ART_HOST_GTEST_EXECUTABLES) $(HOST_CORE_IMG_OUT) $(ART_HOST_OUT_SHARED_LIBRARIES)/libjavacore$(ART_HOST_SHLIB_EXTENSION) .PHONY: build-art-target -build-art-target: $(ART_TARGET_EXECUTABLES) $(ART_TARGET_TEST_EXECUTABLES) $(TARGET_CORE_IMG_OUT) $(TARGET_OUT)/lib/libjavacore.so +build-art-target: $(ART_TARGET_EXECUTABLES) $(ART_TARGET_GTEST_EXECUTABLES) $(TARGET_CORE_IMG_OUT) $(TARGET_OUT_SHARED_LIBRARIES)/libjavacore.so ######################################################################## -# 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.odex $(TARGET_BOOT_IMG_OUT) $(OATDUMP) - $(OATDUMP) --oat-file=$< --output=/tmp/Calculator.oatdump.txt - @echo Output in /tmp/Calculator.oatdump.txt +# "m art-host" for just building the files needed to run the art script +.PHONY: art-host +ifeq ($(HOST_PREFER_32_BIT),true) +art-host: $(HOST_OUT_EXECUTABLES)/art $(HOST_OUT)/bin/dalvikvm32 $(HOST_OUT)/lib/libart.so $(HOST_OUT)/bin/dex2oat $(HOST_OUT)/bin/patchoat $(HOST_CORE_IMG_OUT) $(HOST_OUT)/lib/libjavacore.so $(HOST_OUT)/bin/dalvikvm +else +art-host: $(HOST_OUT_EXECUTABLES)/art $(HOST_OUT)/bin/dalvikvm64 $(HOST_OUT)/bin/dalvikvm32 $(HOST_OUT)/lib/libart.so $(HOST_OUT)/bin/dex2oat $(HOST_OUT)/bin/patchoat $(HOST_CORE_IMG_OUT) $(HOST_OUT)/lib/libjavacore.so $(HOST_OUT)/lib64/libjavacore.so $(HOST_OUT)/bin/dalvikvm endif -######################################################################## -# cpplint targets to style check art source files - -include $(art_build_path)/Android.cpplint.mk +.PHONY: art-host-debug +art-host-debug: art-host $(HOST_OUT)/lib/libartd.so $(HOST_OUT)/bin/dex2oatd $(HOST_OUT)/bin/patchoatd ######################################################################## # targets to switch back and forth from libdvm to libart @@ -349,20 +376,83 @@ include $(art_build_path)/Android.cpplint.mk .PHONY: use-art use-art: adb root && sleep 3 - adb shell setprop persist.sys.dalvik.vm.lib libart.so - adb reboot + adb shell stop + adb shell setprop persist.sys.dalvik.vm.lib.2 libart.so + adb shell start .PHONY: use-artd use-artd: adb root && sleep 3 - adb shell setprop persist.sys.dalvik.vm.lib libartd.so - adb reboot + adb shell stop + adb shell setprop persist.sys.dalvik.vm.lib.2 libartd.so + adb shell start .PHONY: use-dalvik use-dalvik: adb root && sleep 3 - adb shell setprop persist.sys.dalvik.vm.lib libdvm.so - adb reboot + adb shell stop + adb shell setprop persist.sys.dalvik.vm.lib.2 libdvm.so + adb shell start + +.PHONY: use-art-full +use-art-full: + adb root && sleep 3 + adb shell stop + adb shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* + adb shell setprop dalvik.vm.dex2oat-filter "" + adb shell setprop dalvik.vm.image-dex2oat-filter "" + adb shell setprop persist.sys.dalvik.vm.lib.2 libart.so + adb shell start + +.PHONY: use-artd-full +use-artd-full: + adb root && sleep 3 + adb shell stop + adb shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* + adb shell setprop dalvik.vm.dex2oat-filter "" + adb shell setprop dalvik.vm.image-dex2oat-filter "" + adb shell setprop persist.sys.dalvik.vm.lib.2 libartd.so + adb shell start + +.PHONY: use-art-smart +use-art-smart: + adb root && sleep 3 + adb shell stop + adb shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* + adb shell setprop dalvik.vm.dex2oat-filter "interpret-only" + adb shell setprop dalvik.vm.image-dex2oat-filter "" + adb shell setprop persist.sys.dalvik.vm.lib.2 libart.so + adb shell start + +.PHONY: use-art-interpret-only +use-art-interpret-only: + adb root && sleep 3 + adb shell stop + adb shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* + adb shell setprop dalvik.vm.dex2oat-filter "interpret-only" + adb shell setprop dalvik.vm.image-dex2oat-filter "interpret-only" + adb shell setprop persist.sys.dalvik.vm.lib.2 libart.so + adb shell start + +.PHONY: use-artd-interpret-only +use-artd-interpret-only: + adb root && sleep 3 + adb shell stop + adb shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* + adb shell setprop dalvik.vm.dex2oat-filter "interpret-only" + adb shell setprop dalvik.vm.image-dex2oat-filter "interpret-only" + adb shell setprop persist.sys.dalvik.vm.lib.2 libartd.so + adb shell start + +.PHONY: use-art-verify-none +use-art-verify-none: + adb root && sleep 3 + adb shell stop + adb shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* + adb shell setprop dalvik.vm.dex2oat-filter "verify-none" + adb shell setprop dalvik.vm.image-dex2oat-filter "verify-none" + adb shell setprop persist.sys.dalvik.vm.lib.2 libart.so + adb shell start ######################################################################## diff --git a/CleanSpec.mk b/CleanSpec.mk new file mode 100644 index 00000000000..341df784001 --- /dev/null +++ b/CleanSpec.mk @@ -0,0 +1,55 @@ +# Copyright (C) 2007 The Android Open 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. +# + +# If you don't need to do a full clean build but would like to touch +# a file or delete some intermediate files, add a clean step to the end +# of the list. These steps will only be run once, if they haven't been +# run before. +# +# E.g.: +# $(call add-clean-step, touch -c external/sqlite/sqlite3.h) +# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates) +# +# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with +# files that are missing or have been moved. +# +# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory. +# Use $(OUT_DIR) to refer to the "out" directory. +# +# If you need to re-do something that's already mentioned, just copy +# the command and add it to the bottom of the list. E.g., if a change +# that you made last week required touching a file and a change you +# made today requires touching the same file, just copy the old +# touch step and add it to the end of the list. +# +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ + +# For example: +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates) +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates) +#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) +#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) + +# Switching to jemalloc requires deleting these files. +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libart_*) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libartd_*) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libart_*) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libartd_*) + +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ diff --git a/NOTICE b/NOTICE index faed58a1532..d27f6a67145 100644 --- a/NOTICE +++ b/NOTICE @@ -188,3 +188,79 @@ END OF TERMS AND CONDITIONS +------------------------------------------------------------------- + +For art/runtime/elf.h derived from external/llvm/include/llvm/Support/ELF.h + +============================================================================== +LLVM Release License +============================================================================== +University of Illinois/NCSA +Open Source License + +Copyright (c) 2003-2014 University of Illinois at Urbana-Champaign. +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== +Copyrights and Licenses for Third Party Software Distributed with LLVM: +============================================================================== +The LLVM software contains code written by third parties. Such software will +have its own individual LICENSE.TXT file in the directory in which it appears. +This file will describe the copyrights, license, and restrictions which apply +to that code. + +The disclaimer of warranty in the University of Illinois Open Source License +applies to all code in the LLVM Distribution, and nothing in any of the +other licenses gives permission to use the names of the LLVM Team or the +University of Illinois to endorse or promote products derived from this +Software. + +The following pieces of software have additional or alternate copyrights, +licenses, and/or restrictions: + +Program Directory +------- --------- +Autoconf llvm/autoconf + llvm/projects/ModuleMaker/autoconf +Google Test llvm/utils/unittest/googletest +OpenBSD regex llvm/lib/Support/{reg*, COPYRIGHT.regex} +pyyaml tests llvm/test/YAMLParser/{*.data, LICENSE.TXT} +ARM contributions llvm/lib/Target/ARM/LICENSE.TXT +md5 contributions llvm/lib/Support/MD5.cpp llvm/include/llvm/Support/MD5.h + +------------------------------------------------------------------- diff --git a/build/Android.common.mk b/build/Android.common.mk index ac1be1e28ad..39a734d2edd 100644 --- a/build/Android.common.mk +++ b/build/Android.common.mk @@ -17,207 +17,61 @@ ifndef ANDROID_COMMON_MK ANDROID_COMMON_MK = true -# 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 ?= $(WITH_HOST_DALVIK) -ART_BUILD_HOST_DEBUG ?= $(WITH_HOST_DALVIK) - -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 - -# -# Used to enable smart mode -# -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 -ifeq ($(WITH_ART_SMALL_MODE), true) -ART_SMALL_MODE := true -endif - -# -# Used to enable SEA mode -# -ART_SEA_IR_MODE := false -ifneq ($(wildcard art/SEA_IR_ART),) -$(info Enabling ART_SEA_IR_MODE because of existence of art/SEA_IR_ART) -ART_SEA_IR_MODE := true -endif -ifeq ($(WITH_ART_SEA_IR_MODE), true) -ART_SEA_IR_MODE := true -endif - -# -# Used to enable portable mode -# -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 +ART_TARGET_SUPPORTED_ARCH := arm arm64 mips x86 x86_64 +ART_HOST_SUPPORTED_ARCH := x86 x86_64 + +ifeq (,$(filter $(TARGET_ARCH),$(ART_TARGET_SUPPORTED_ARCH))) +$(warning unsupported TARGET_ARCH=$(TARGET_ARCH)) +endif +ifeq (,$(filter $(HOST_ARCH),$(ART_HOST_SUPPORTED_ARCH))) +$(warning unsupported HOST_ARCH=$(HOST_ARCH)) +endif + +# Primary vs. secondary +2ND_TARGET_ARCH := $(TARGET_2ND_ARCH) +TARGET_INSTRUCTION_SET_FEATURES := $(DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) +2ND_TARGET_INSTRUCTION_SET_FEATURES := $($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) +ifdef TARGET_2ND_ARCH + ifneq ($(filter %64,$(TARGET_ARCH)),) + ART_PHONY_TEST_TARGET_SUFFIX := 64 + 2ND_ART_PHONY_TEST_TARGET_SUFFIX := 32 + ART_TARGET_ARCH_32 := $(TARGET_2ND_ARCH) + ART_TARGET_ARCH_64 := $(TARGET_ARCH) + else + # TODO: ??? + $(error Do not know what to do with this multi-target configuration!) + endif +else + ART_PHONY_TEST_TARGET_SUFFIX := 32 + 2ND_ART_PHONY_TEST_TARGET_SUFFIX := + ART_TARGET_ARCH_32 := $(TARGET_ARCH) + ART_TARGET_ARCH_64 := 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_HOST_SHLIB_EXTENSION := $(HOST_SHLIB_SUFFIX) ART_HOST_SHLIB_EXTENSION ?= .so - -ART_C_INCLUDES := \ - external/gtest/include \ - external/valgrind/main/include \ - external/zlib \ - frameworks/compile/mclinker/include - -art_cflags := \ - -fno-rtti \ - -std=gnu++11 \ - -ggdb3 \ - -Wall \ - -Werror \ - -Wextra \ - -Wstrict-aliasing=3 \ - -fstrict-aliasing - -ifeq ($(ART_SMALL_MODE),true) - art_cflags += -DART_SMALL_MODE=1 -endif - -ifeq ($(ART_SEA_IR_MODE),true) - art_cflags += -DART_SEA_IR_MODE=1 -endif - -ifeq ($(HOST_OS),linux) - art_non_debug_cflags := \ - -Wframe-larger-than=1728 -endif - -art_non_debug_cflags := \ - -O3 - -art_debug_cflags := \ - -O1 \ - -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 +ifeq ($(HOST_PREFER_32_BIT),true) + ART_PHONY_TEST_HOST_SUFFIX := 32 + 2ND_ART_PHONY_TEST_HOST_SUFFIX := + ART_HOST_ARCH_32 := x86 + ART_HOST_ARCH_64 := + ART_HOST_ARCH := x86 + 2ND_ART_HOST_ARCH := + 2ND_HOST_ARCH := + ART_HOST_LIBRARY_PATH := $(HOST_LIBRARY_PATH) + ART_HOST_OUT_SHARED_LIBRARIES := $(2ND_HOST_OUT_SHARED_LIBRARIES) + 2ND_ART_HOST_OUT_SHARED_LIBRARIES := 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 - -# Enable thread-safety for GCC 4.6 on the target but not for GCC 4.7 where this feature was removed. -ifneq ($(filter 4.6 4.6.%, $(TARGET_GCC_VERSION)),) - ART_TARGET_CFLAGS += -Wthread-safety -else - # Warn if not using GCC 4.6 for target builds when not doing a top-level or 'mma' build. - ifneq ($(ONE_SHOT_MAKEFILE),) - # Enable target GCC 4.6 with: export TARGET_GCC_VERSION_EXP=4.6 - $(info Using target GCC $(TARGET_GCC_VERSION) disables thread-safety checks.) - endif -endif -# We build with GCC 4.6 on the host. -ART_HOST_CFLAGS += -Wthread-safety - -# Make host builds easier to debug and profile by not omitting the frame pointer. -ART_HOST_CFLAGS += -fno-omit-frame-pointer - -# 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 - -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 + ART_PHONY_TEST_HOST_SUFFIX := 64 + 2ND_ART_PHONY_TEST_HOST_SUFFIX := 32 + ART_HOST_ARCH_32 := x86 + ART_HOST_ARCH_64 := x86_64 + ART_HOST_ARCH := x86_64 + 2ND_ART_HOST_ARCH := x86 + 2ND_HOST_ARCH := x86 + ART_HOST_LIBRARY_PATH := $(HOST_LIBRARY_PATH) + ART_HOST_OUT_SHARED_LIBRARIES := $(HOST_OUT_SHARED_LIBRARIES) + 2ND_ART_HOST_OUT_SHARED_LIBRARIES := $(2ND_HOST_OUT_SHARED_LIBRARIES) endif endif # ANDROID_COMMON_MK diff --git a/build/Android.common_build.mk b/build/Android.common_build.mk new file mode 100644 index 00000000000..eafb2a057c6 --- /dev/null +++ b/build/Android.common_build.mk @@ -0,0 +1,356 @@ +# +# 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 ANDROID_COMMON_BUILD_MK +ANDROID_COMMON_BUILD_MK = true + +include art/build/Android.common.mk + +# 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 ?= false +ART_BUILD_HOST_NDEBUG ?= false +ART_BUILD_HOST_DEBUG ?= false + +ifeq ($(ART_BUILD_TARGET_NDEBUG),false) +$(info Disabling ART_BUILD_TARGET_NDEBUG) +endif +ifeq ($(ART_BUILD_TARGET_DEBUG),true) +$(info Enabling ART_BUILD_TARGET_DEBUG) +endif +ifeq ($(ART_BUILD_HOST_NDEBUG),true) +$(info Enabling ART_BUILD_HOST_NDEBUG) +endif +ifeq ($(ART_BUILD_HOST_DEBUG),true) +$(info Enabling ART_BUILD_HOST_DEBUG) +endif + +# +# Used to enable smart mode +# +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 +ifeq ($(WITH_ART_SMALL_MODE), true) +ART_SMALL_MODE := true +endif + +# +# Used to enable SEA mode +# +ART_SEA_IR_MODE := false +ifneq ($(wildcard art/SEA_IR_ART),) +$(info Enabling ART_SEA_IR_MODE because of existence of art/SEA_IR_ART) +ART_SEA_IR_MODE := true +endif +ifeq ($(WITH_ART_SEA_IR_MODE), true) +ART_SEA_IR_MODE := true +endif + +# +# Used to enable portable mode +# +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 + +# +# Used to enable optimizing compiler +# +ART_USE_OPTIMIZING_COMPILER := false +ifneq ($(wildcard art/USE_OPTIMIZING_COMPILER),) +$(info Enabling ART_USE_OPTIMIZING_COMPILER because of existence of art/USE_OPTIMIZING_COMPILER) +ART_USE_OPTIMIZING_COMPILER := true +endif +ifeq ($(WITH_ART_USE_OPTIMIZING_COMPILER), true) +ART_USE_OPTIMIZING_COMPILER := true +endif + +ifeq ($(ART_USE_OPTIMIZING_COMPILER),true) +DEX2OAT_FLAGS := --compiler-backend=Optimizing +DALVIKVM_FLAGS += -Xcompiler-option --compiler-backend=Optimizing +endif + +# +# Used to change the default GC. Valid values are CMS, SS, GSS. The default is CMS. +# +ART_DEFAULT_GC_TYPE ?= CMS +ART_DEFAULT_GC_TYPE_CFLAGS := -DART_DEFAULT_GC_TYPE_IS_$(ART_DEFAULT_GC_TYPE) + +ifeq ($(ART_USE_PORTABLE_COMPILER),true) + LLVM_ROOT_PATH := external/llvm + # Don't fail a dalvik minimal host build. + -include $(LLVM_ROOT_PATH)/llvm.mk +endif + +# Clang build support. + +# Host. +ART_HOST_CLANG := false +ifneq ($(WITHOUT_HOST_CLANG),true) + # By default, host builds use clang for better warnings. + ART_HOST_CLANG := true +endif + +# Clang on the target. Target builds use GCC by default. +ART_TARGET_CLANG := +ART_TARGET_CLANG_arm := +ART_TARGET_CLANG_arm64 := +ART_TARGET_CLANG_mips := +ART_TARGET_CLANG_x86 := +ART_TARGET_CLANG_x86_64 := + +define set-target-local-clang-vars + LOCAL_CLANG := $(ART_TARGET_CLANG) + $(foreach arch,$(ART_TARGET_SUPPORTED_ARCH), + ifneq ($$(ART_TARGET_CLANG_$(arch)),) + LOCAL_CLANG_$(arch) := $$(ART_TARGET_CLANG_$(arch)) + endif) +endef + +ART_CPP_EXTENSION := .cc + +ART_C_INCLUDES := \ + external/gtest/include \ + external/valgrind/main/include \ + external/valgrind/main \ + external/vixl/src \ + external/zlib \ + frameworks/compile/mclinker/include + +art_cflags := \ + -fno-rtti \ + -std=gnu++11 \ + -ggdb3 \ + -Wall \ + -Werror \ + -Wextra \ + -Wno-sign-promo \ + -Wno-unused-parameter \ + -Wstrict-aliasing \ + -fstrict-aliasing \ + -fvisibility=protected + +ART_TARGET_CLANG_CFLAGS := +ART_TARGET_CLANG_CFLAGS_arm := +ART_TARGET_CLANG_CFLAGS_arm64 := +ART_TARGET_CLANG_CFLAGS_mips := +ART_TARGET_CLANG_CFLAGS_x86 := +ART_TARGET_CLANG_CFLAGS_x86_64 := + +# these are necessary for Clang ARM64 ART builds +ART_TARGET_CLANG_CFLAGS_arm64 += \ + -Wno-implicit-exception-spec-mismatch \ + -DNVALGRIND \ + -Wno-unused-value + +ifeq ($(ART_SMALL_MODE),true) + art_cflags += -DART_SMALL_MODE=1 +endif + +ifeq ($(ART_SEA_IR_MODE),true) + art_cflags += -DART_SEA_IR_MODE=1 +endif + +art_non_debug_cflags := \ + -O3 + +art_host_non_debug_cflags := \ + $(art_non_debug_cflags) + +art_target_non_debug_cflags := \ + $(art_non_debug_cflags) + +ifeq ($(HOST_OS),linux) + # Larger frame-size for host clang builds today + art_host_non_debug_cflags += -Wframe-larger-than=3000 + art_target_non_debug_cflags += -Wframe-larger-than=1800 +endif + +# FIXME: upstream LLVM has a vectorizer bug that needs to be fixed +ART_TARGET_CLANG_CFLAGS_arm64 += \ + -fno-vectorize + +art_debug_cflags := \ + -O1 \ + -DDYNAMIC_ANNOTATIONS_ENABLED=1 \ + -UNDEBUG + +ifndef LIBART_IMG_HOST_BASE_ADDRESS + $(error LIBART_IMG_HOST_BASE_ADDRESS unset) +endif +ART_HOST_CFLAGS := $(art_cflags) -DANDROID_SMP=1 -DART_BASE_ADDRESS=$(LIBART_IMG_HOST_BASE_ADDRESS) +ART_HOST_CFLAGS += -DART_DEFAULT_INSTRUCTION_SET_FEATURES=default +ART_HOST_CFLAGS += $(ART_DEFAULT_GC_TYPE_CFLAGS) + +ifndef LIBART_IMG_TARGET_BASE_ADDRESS + $(error LIBART_IMG_TARGET_BASE_ADDRESS unset) +endif +ART_TARGET_CFLAGS := $(art_cflags) -DART_TARGET -DART_BASE_ADDRESS=$(LIBART_IMG_TARGET_BASE_ADDRESS) + +ifndef LIBART_IMG_HOST_MIN_BASE_ADDRESS_DELTA + LIBART_IMG_HOST_MIN_BASE_ADDRESS_DELTA=-0x1000000 +endif +ifndef LIBART_IMG_HOST_MAX_BASE_ADDRESS_DELTA + LIBART_IMG_HOST_MAX_BASE_ADDRESS_DELTA=0x1000000 +endif +ART_HOST_CFLAGS += -DART_BASE_ADDRESS_MIN_DELTA=$(LIBART_IMG_HOST_MIN_BASE_ADDRESS_DELTA) +ART_HOST_CFLAGS += -DART_BASE_ADDRESS_MAX_DELTA=$(LIBART_IMG_HOST_MAX_BASE_ADDRESS_DELTA) + +ifndef LIBART_IMG_TARGET_MIN_BASE_ADDRESS_DELTA + LIBART_IMG_TARGET_MIN_BASE_ADDRESS_DELTA=-0x1000000 +endif +ifndef LIBART_IMG_TARGET_MAX_BASE_ADDRESS_DELTA + LIBART_IMG_TARGET_MAX_BASE_ADDRESS_DELTA=0x1000000 +endif +ART_TARGET_CFLAGS += -DART_BASE_ADDRESS_MIN_DELTA=$(LIBART_IMG_TARGET_MIN_BASE_ADDRESS_DELTA) +ART_TARGET_CFLAGS += -DART_BASE_ADDRESS_MAX_DELTA=$(LIBART_IMG_TARGET_MAX_BASE_ADDRESS_DELTA) + +ART_TARGET_LDFLAGS := +ifeq ($(TARGET_CPU_SMP),true) + ART_TARGET_CFLAGS += -DANDROID_SMP=1 +else + ifeq ($(TARGET_CPU_SMP),false) + ART_TARGET_CFLAGS += -DANDROID_SMP=0 + else + $(warning TARGET_CPU_SMP should be (true|false), found $(TARGET_CPU_SMP)) + # Make sure we emit barriers for the worst case. + ART_TARGET_CFLAGS += -DANDROID_SMP=1 + endif +endif +ART_TARGET_CFLAGS += $(ART_DEFAULT_GC_TYPE_CFLAGS) + +# DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES is set in ../build/core/dex_preopt.mk based on +# the TARGET_CPU_VARIANT +ifeq ($(DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES),) +$(error Required DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES is not set) +endif +ART_TARGET_CFLAGS += -DART_DEFAULT_INSTRUCTION_SET_FEATURES=$(DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) + +# Enable thread-safety for GCC 4.6, and clang, but not for GCC 4.7 or later where this feature was +# removed. Warn when -Wthread-safety is not used. +ifneq ($(filter 4.6 4.6.%, $(TARGET_GCC_VERSION)),) + ART_TARGET_CFLAGS += -Wthread-safety +else + # FIXME: add -Wthread-safety when the problem is fixed + ifeq ($(ART_TARGET_CLANG),true) + ART_TARGET_CFLAGS += + else + # Warn if -Wthread-safety is not supported and not doing a top-level or 'mma' build. + ifneq ($(ONE_SHOT_MAKEFILE),) + # Enable target GCC 4.6 with: export TARGET_GCC_VERSION_EXP=4.6 + $(info Using target GCC $(TARGET_GCC_VERSION) disables thread-safety checks.) + endif + endif +endif +# We compile with GCC 4.6 or clang on the host, both of which support -Wthread-safety. +ART_HOST_CFLAGS += -Wthread-safety + +# To use oprofile_android --callgraph, uncomment this and recompile with "mmm art -B -j16" +# ART_TARGET_CFLAGS += -fno-omit-frame-pointer -marm -mapcs + +# Addition CPU specific CFLAGS. +ifeq ($(TARGET_ARCH),arm) + ifneq ($(filter cortex-a15, $(TARGET_CPU_VARIANT)),) + # Fake a ARM feature for LPAE support. + ART_TARGET_CFLAGS += -D__ARM_FEATURE_LPAE=1 + endif +endif + +ART_HOST_NON_DEBUG_CFLAGS := $(art_host_non_debug_cflags) +ART_TARGET_NON_DEBUG_CFLAGS := $(art_target_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) + +# $(1): ndebug_or_debug +define set-target-local-cflags-vars + LOCAL_CFLAGS += $(ART_TARGET_CFLAGS) + LOCAL_CFLAGS_x86 += $(ART_TARGET_CFLAGS_x86) + LOCAL_LDFLAGS += $(ART_TARGET_LDFLAGS) + art_target_cflags_ndebug_or_debug := $(1) + ifeq ($$(art_target_cflags_ndebug_or_debug),debug) + LOCAL_CFLAGS += $(ART_TARGET_DEBUG_CFLAGS) + else + LOCAL_CFLAGS += $(ART_TARGET_NON_DEBUG_CFLAGS) + endif + + # TODO: Also set when ART_TARGET_CLANG_$(arch)!=false and ART_TARGET_CLANG==true + $(foreach arch,$(ART_SUPPORTED_ARCH), + ifeq ($$(ART_TARGET_CLANG_$(arch)),true) + LOCAL_CFLAGS_$(arch) += $$(ART_TARGET_CLANG_CFLAGS_$(arch)) + endif) + + # Clear locally used variables. + art_target_cflags_ndebug_or_debug := +endef + +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 + +# Clear locally defined variables that aren't necessary in the rest of the build system. +ART_DEFAULT_GC_TYPE := +ART_DEFAULT_GC_TYPE_CFLAGS := +art_cflags := +art_target_non_debug_cflags := +art_host_non_debug_cflags := +art_non_debug_cflags := + +endif # ANDROID_COMMON_BUILD_MK diff --git a/build/Android.common_path.mk b/build/Android.common_path.mk new file mode 100644 index 00000000000..10695b6be72 --- /dev/null +++ b/build/Android.common_path.mk @@ -0,0 +1,83 @@ +# +# 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 ANDROID_COMMON_PATH_MK +ANDROID_COMMON_PATH_MK := true + +include art/build/Android.common.mk + +# Directory used for dalvik-cache on device. +ART_TARGET_DALVIK_CACHE_DIR := /data/dalvik-cache + +# Directory used for gtests on device. +# $(TARGET_OUT_DATA_NATIVE_TESTS) will evaluate to the nativetest directory in the target part on +# the host, so we can strip everything but the directory to find out whether it is "nativetest" or +# "nativetest64." +ART_TARGET_NATIVETEST_DIR := /data/$(notdir $(TARGET_OUT_DATA_NATIVE_TESTS))/art + +ART_TARGET_NATIVETEST_OUT := $(TARGET_OUT_DATA_NATIVE_TESTS)/art + +# Directory used for oat tests on device. +ART_TARGET_TEST_DIR := /data/art-test +ART_TARGET_TEST_OUT := $(TARGET_OUT_DATA)/art-test + +# Directory used for temporary test files on the host. +ifneq ($(TMPDIR),) +ART_HOST_TEST_DIR := $(TMPDIR)/test-art-$(shell echo $$PPID) +else +ART_HOST_TEST_DIR := /tmp/test-art-$(shell echo $$PPID) +endif + +# Core.oat location on the device. +TARGET_CORE_OAT := $(ART_TARGET_TEST_DIR)/$(DEX2OAT_TARGET_ARCH)/core.oat +ifdef TARGET_2ND_ARCH +2ND_TARGET_CORE_OAT := $(ART_TARGET_TEST_DIR)/$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_ARCH)/core.oat +endif + +# Core.oat locations under the out directory. +HOST_CORE_OAT_OUT := $(HOST_OUT_JAVA_LIBRARIES)/$(ART_HOST_ARCH)/core.oat +ifneq ($(HOST_PREFER_32_BIT),true) +2ND_HOST_CORE_OAT_OUT := $(HOST_OUT_JAVA_LIBRARIES)/$(2ND_ART_HOST_ARCH)/core.oat +endif +TARGET_CORE_OAT_OUT := $(ART_TARGET_TEST_OUT)/$(DEX2OAT_TARGET_ARCH)/core.oat +ifdef TARGET_2ND_ARCH +2ND_TARGET_CORE_OAT_OUT := $(ART_TARGET_TEST_OUT)/$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_ARCH)/core.oat +endif + +# Core.art locations under the out directory. +HOST_CORE_IMG_OUT := $(HOST_OUT_JAVA_LIBRARIES)/$(ART_HOST_ARCH)/core.art +ifneq ($(HOST_PREFER_32_BIT),true) +2ND_HOST_CORE_IMG_OUT := $(HOST_OUT_JAVA_LIBRARIES)/$(2ND_ART_HOST_ARCH)/core.art +endif +TARGET_CORE_IMG_OUT := $(ART_TARGET_TEST_OUT)/$(DEX2OAT_TARGET_ARCH)/core.art +ifdef TARGET_2ND_ARCH +2ND_TARGET_CORE_IMG_OUT := $(ART_TARGET_TEST_OUT)/$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_ARCH)/core.art +endif + +# Oat location of core.art. +HOST_CORE_IMG_LOCATION := $(HOST_OUT_JAVA_LIBRARIES)/core.art +TARGET_CORE_IMG_LOCATION := $(ART_TARGET_TEST_OUT)/core.art + +# Jar files for core.art. +TARGET_CORE_JARS := core-libart 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) +endif # ANDROID_COMMON_PATH_MK diff --git a/build/Android.common_test.mk b/build/Android.common_test.mk new file mode 100644 index 00000000000..7e38157e93e --- /dev/null +++ b/build/Android.common_test.mk @@ -0,0 +1,173 @@ +# +# 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 ANDROID_COMMON_TEST_MK +ANDROID_COMMON_TEST_MK = true + +include art/build/Android.common_path.mk + +# We need to set a define for the nativetest dir so that common_runtime_test will know the right +# path. (The problem is being a 32b test on 64b device, which is still located in nativetest64). +ART_TARGET_CFLAGS += -DART_TARGET_NATIVETEST_DIR=${ART_TARGET_NATIVETEST_DIR} + +# List of known broken tests that we won't attempt to execute. The test name must be the full +# rule name such as test-art-host-oat-optimizing-HelloWorld64. +ART_TEST_KNOWN_BROKEN := \ + test-art-host-run-test-gcstress-optimizing-no-prebuild-004-SignalTest32 \ + test-art-host-run-test-gcstress-optimizing-prebuild-004-SignalTest32 \ + test-art-host-run-test-gcstress-optimizing-norelocate-004-SignalTest32 \ + test-art-host-run-test-gcstress-optimizing-relocate-004-SignalTest32 \ + test-art-host-run-test-gcverify-optimizing-no-prebuild-004-SignalTest32 \ + test-art-host-run-test-gcverify-optimizing-prebuild-004-SignalTest32 \ + test-art-host-run-test-gcverify-optimizing-norelocate-004-SignalTest32 \ + test-art-host-run-test-gcverify-optimizing-relocate-004-SignalTest32 \ + test-art-host-run-test-optimizing-no-prebuild-004-SignalTest32 \ + test-art-host-run-test-optimizing-prebuild-004-SignalTest32 \ + test-art-host-run-test-optimizing-norelocate-004-SignalTest32 \ + test-art-host-run-test-optimizing-relocate-004-SignalTest32 \ + test-art-target-run-test-gcstress-optimizing-prebuild-004-SignalTest32 \ + test-art-target-run-test-gcstress-optimizing-norelocate-004-SignalTest32 \ + test-art-target-run-test-gcstress-default-prebuild-004-SignalTest32 \ + test-art-target-run-test-gcstress-default-norelocate-004-SignalTest32 \ + test-art-target-run-test-gcstress-optimizing-relocate-004-SignalTest32 \ + test-art-target-run-test-gcstress-default-relocate-004-SignalTest32 \ + test-art-target-run-test-gcstress-optimizing-no-prebuild-004-SignalTest32 \ + test-art-target-run-test-gcstress-default-no-prebuild-004-SignalTest32 + +# List of known failing tests that when executed won't cause test execution to not finish. +# The test name must be the full rule name such as test-art-host-oat-optimizing-HelloWorld64. +ART_TEST_KNOWN_FAILING := + +# Keep going after encountering a test failure? +ART_TEST_KEEP_GOING ?= true + +# Do you want all tests, even those that are time consuming? +ART_TEST_FULL ?= true + +# Do you want optimizing compiler tests run? +ART_TEST_OPTIMIZING ?= $(ART_TEST_FULL) + +# Do you want tracing tests run? +ART_TEST_TRACE ?= $(ART_TEST_FULL) + +# Do you want tests with GC verification enabled run? +ART_TEST_GC_VERIFY ?= $(ART_TEST_FULL) + +# Do you want tests with the GC stress mode enabled run? +ART_TEST_GC_STRESS ?= $(ART_TEST_FULL) + +# Do you want run-tests with relocation enabled? +ART_TEST_RUN_TEST_RELOCATE ?= $(ART_TEST_FULL) + +# Do you want run-tests with relocation disabled? +ART_TEST_RUN_TEST_NO_RELOCATE ?= $(ART_TEST_FULL) + +# Do you want run-tests with prebuild disabled? +ART_TEST_RUN_TEST_NO_PREBUILD ?= $(ART_TEST_FULL) + +# Do you want run-tests with prebuild enabled? +ART_TEST_RUN_TEST_PREBUILD ?= true + +# Do you want failed tests to have their artifacts cleaned up? +ART_TEST_RUN_TEST_ALWAYS_CLEAN ?= true + +# Define the command run on test failure. $(1) is the name of the test. Executed by the shell. +define ART_TEST_FAILED + ( [ -f $(ART_HOST_TEST_DIR)/skipped/$(1) ] || \ + (mkdir -p $(ART_HOST_TEST_DIR)/failed/ && touch $(ART_HOST_TEST_DIR)/failed/$(1) && \ + echo $(ART_TEST_KNOWN_FAILING) | grep -q $(1) \ + && (echo -e "$(1) \e[91mKNOWN FAILURE\e[0m") \ + || (echo -e "$(1) \e[91mFAILED\e[0m" >&2 ))) +endef + +# Define the command run on test success. $(1) is the name of the test. Executed by the shell. +# The command checks prints "PASSED" then checks to see if this was a top-level make target (e.g. +# "mm test-art-host-oat-HelloWorld32"), if it was then it does nothing, otherwise it creates a file +# to be printed in the passing test summary. +define ART_TEST_PASSED + ( echo -e "$(1) \e[92mPASSED\e[0m" && \ + (echo $(MAKECMDGOALS) | grep -q $(1) || \ + (mkdir -p $(ART_HOST_TEST_DIR)/passed/ && touch $(ART_HOST_TEST_DIR)/passed/$(1)))) +endef + +# Define the command run on test success of multiple prerequisites. $(1) is the name of the test. +# When the test is a top-level make target then a summary of the ran tests is produced. Executed by +# the shell. +define ART_TEST_PREREQ_FINISHED + (echo -e "$(1) \e[32mCOMPLETE\e[0m" && \ + (echo $(MAKECMDGOALS) | grep -q -v $(1) || \ + (([ -d $(ART_HOST_TEST_DIR)/passed/ ] \ + && (echo -e "\e[92mPASSING TESTS\e[0m" && ls -1 $(ART_HOST_TEST_DIR)/passed/) \ + || (echo -e "\e[91mNO TESTS PASSED\e[0m")) && \ + ([ -d $(ART_HOST_TEST_DIR)/skipped/ ] \ + && (echo -e "\e[93mSKIPPED TESTS\e[0m" && ls -1 $(ART_HOST_TEST_DIR)/skipped/) \ + || (echo -e "\e[92mNO TESTS SKIPPED\e[0m")) && \ + ([ -d $(ART_HOST_TEST_DIR)/failed/ ] \ + && (echo -e "\e[91mFAILING TESTS\e[0m" >&2 && ls -1 $(ART_HOST_TEST_DIR)/failed/ >&2) \ + || (echo -e "\e[92mNO TESTS FAILED\e[0m")) \ + && ([ ! -d $(ART_HOST_TEST_DIR)/failed/ ] && rm -r $(ART_HOST_TEST_DIR) \ + || (rm -r $(ART_HOST_TEST_DIR) && false))))) +endef + +# Define the command executed by the shell ahead of running an art test. $(1) is the name of the +# test. +define ART_TEST_SKIP + ((echo $(ART_TEST_KNOWN_BROKEN) | grep -q -v $(1) \ + && ([ ! -d $(ART_HOST_TEST_DIR)/failed/ ] || [ $(ART_TEST_KEEP_GOING) = true ])\ + && echo -e "$(1) \e[95mRUNNING\e[0m") \ + || ((mkdir -p $(ART_HOST_TEST_DIR)/skipped/ && touch $(ART_HOST_TEST_DIR)/skipped/$(1) \ + && ([ -d $(ART_HOST_TEST_DIR)/failed/ ] \ + && echo -e "$(1) \e[93mSKIPPING DUE TO EARLIER FAILURE\e[0m") \ + || echo -e "$(1) \e[93mSKIPPING BROKEN TEST\e[0m") && false)) +endef + +# Create a build rule to create the dex file for a test. +# $(1): module prefix, e.g. art-test-dex +# $(2): input test directory in art/test, e.g. HelloWorld +# $(3): target output module path (default module path is used on host) +# $(4): additional dependencies +# $(5): a make variable used to collate target dependencies, e.g ART_TEST_TARGET_OAT_HelloWorld_DEX +# $(6): a make variable used to collate host dependencies, e.g ART_TEST_HOST_OAT_HelloWorld_DEX +define build-art-test-dex + ifeq ($(ART_BUILD_TARGET),true) + include $(CLEAR_VARS) + LOCAL_MODULE := $(1)-$(2) + LOCAL_SRC_FILES := $(call all-java-files-under, $(2)) + LOCAL_NO_STANDARD_LIBRARIES := true + LOCAL_DEX_PREOPT := false + LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common_test.mk $(4) + LOCAL_MODULE_TAGS := tests + LOCAL_JAVA_LIBRARIES := $(TARGET_CORE_JARS) + LOCAL_MODULE_PATH := $(3) + LOCAL_DEX_PREOPT_IMAGE_LOCATION := $(TARGET_CORE_IMG_OUT) + include $(BUILD_JAVA_LIBRARY) + $(5) := $$(LOCAL_INSTALLED_MODULE) + endif + ifeq ($(ART_BUILD_HOST),true) + include $(CLEAR_VARS) + LOCAL_MODULE := $(1)-$(2) + LOCAL_SRC_FILES := $(call all-java-files-under, $(2)) + LOCAL_NO_STANDARD_LIBRARIES := true + LOCAL_DEX_PREOPT := false + LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common_test.mk $(4) + LOCAL_JAVA_LIBRARIES := $(HOST_CORE_JARS) + LOCAL_DEX_PREOPT_IMAGE := $(HOST_CORE_IMG_LOCATION) + include $(BUILD_HOST_DALVIK_JAVA_LIBRARY) + $(6) := $$(LOCAL_INSTALLED_MODULE) + endif +endef + +endif # ANDROID_COMMON_TEST_MK diff --git a/build/Android.cpplint.mk b/build/Android.cpplint.mk index adb87cb4e96..79f8f5eeaf2 100644 --- a/build/Android.cpplint.mk +++ b/build/Android.cpplint.mk @@ -14,9 +14,11 @@ # limitations under the License. # +include art/build/Android.common_build.mk + ART_CPPLINT := art/tools/cpplint.py ART_CPPLINT_FILTER := --filter=-whitespace/line_length,-build/include,-readability/function,-readability/streams,-readability/todo,-runtime/references,-runtime/sizeof,-runtime/threadsafe_fn,-runtime/printf -ART_CPPLINT_SRC := $(shell find art -name *.h -o -name *$(ART_CPP_EXTENSION) | grep -v art/compiler/llvm/generated/) +ART_CPPLINT_SRC := $(shell find art -name "*.h" -o -name "*$(ART_CPP_EXTENSION)" | grep -v art/compiler/llvm/generated/ | grep -v art/runtime/elf\.h) # "mm cpplint-art" to verify we aren't regressing .PHONY: cpplint-art diff --git a/build/Android.executable.mk b/build/Android.executable.mk index 5cf15be1c17..d887acd1cdd 100644 --- a/build/Android.executable.mk +++ b/build/Android.executable.mk @@ -14,7 +14,7 @@ # limitations under the License. # -include art/build/Android.common.mk +include art/build/Android.common_build.mk ART_HOST_EXECUTABLES ?= ART_TARGET_EXECUTABLES ?= @@ -30,6 +30,7 @@ endif # $(4): extra include directories # $(5): target or host # $(6): ndebug or debug +# $(7): value for LOCAL_MULTILIB (empty means default) define build-art-executable ifneq ($(5),target) ifneq ($(5),host) @@ -48,17 +49,14 @@ define build-art-executable art_c_includes := $(4) art_target_or_host := $(5) art_ndebug_or_debug := $(6) + art_multilib := $(7) 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) art/runtime $$(art_c_includes) - LOCAL_SHARED_LIBRARIES += $$(art_shared_libraries) # libnativehelper + LOCAL_SHARED_LIBRARIES += $$(art_shared_libraries) ifeq ($$(art_ndebug_or_debug),ndebug) LOCAL_MODULE := $$(art_executable) @@ -68,13 +66,8 @@ define build-art-executable 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 + $(call set-target-local-clang-vars) + $(call set-target-local-cflags-vars,$(6)) else # host LOCAL_CLANG := $(ART_HOST_CLANG) LOCAL_CFLAGS += $(ART_HOST_CFLAGS) @@ -83,6 +76,7 @@ define build-art-executable else LOCAL_CFLAGS += $(ART_HOST_NON_DEBUG_CFLAGS) endif + LOCAL_LDLIBS += -lpthread endif ifeq ($$(art_ndebug_or_debug),ndebug) @@ -91,13 +85,20 @@ define build-art-executable LOCAL_SHARED_LIBRARIES += libartd endif - LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common.mk + LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common_build.mk LOCAL_ADDITIONAL_DEPENDENCIES += art/build/Android.executable.mk + ifeq ($$(art_target_or_host),target) + LOCAL_MODULE_TARGET_ARCH := $(ART_SUPPORTED_ARCH) + endif + LOCAL_MULTILIB := $$(art_multilib) + + include external/libcxx/libcxx.mk ifeq ($$(art_target_or_host),target) include $(BUILD_EXECUTABLE) ART_TARGET_EXECUTABLES := $(ART_TARGET_EXECUTABLES) $(TARGET_OUT_EXECUTABLES)/$$(LOCAL_MODULE) else # host + LOCAL_IS_HOST_MODULE := true include $(BUILD_HOST_EXECUTABLE) ART_HOST_EXECUTABLES := $(ART_HOST_EXECUTABLES) $(HOST_OUT_EXECUTABLES)/$$(LOCAL_MODULE) endif diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 4c658a2bb79..a7d852ba3dc 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -14,80 +14,300 @@ # limitations under the License. # +# The path for which all the dex files are relative, not actually the current directory. +LOCAL_PATH := art/test + +include art/build/Android.common_test.mk +include art/build/Android.common_path.mk + +# Subdirectories in art/test which contain dex files used as inputs for gtests. +GTEST_DEX_DIRECTORIES := \ + AbstractMethod \ + AllFields \ + ExceptionHandle \ + GetMethodSignature \ + Interfaces \ + Main \ + MyClass \ + MyClassNatives \ + Nested \ + NonStaticLeafMethods \ + ProtoCompare \ + ProtoCompare2 \ + StaticLeafMethods \ + Statics \ + StaticsFromCode \ + Transaction \ + XandY + +# Create build rules for each dex file recording the dependency. +$(foreach dir,$(GTEST_DEX_DIRECTORIES), $(eval $(call build-art-test-dex,art-gtest,$(dir), \ + $(ART_TARGET_NATIVETEST_OUT),art/build/Android.gtest.mk,ART_TEST_TARGET_GTEST_$(dir)_DEX, \ + ART_TEST_HOST_GTEST_$(dir)_DEX))) + +# Dex file dependencies for each gtest. +ART_GTEST_class_linker_test_DEX_DEPS := Interfaces MyClass Nested Statics StaticsFromCode +ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod +ART_GTEST_dex_file_test_DEX_DEPS := GetMethodSignature +ART_GTEST_exception_test_DEX_DEPS := ExceptionHandle +ART_GTEST_jni_compiler_test_DEX_DEPS := MyClassNatives +ART_GTEST_jni_internal_test_DEX_DEPS := AllFields StaticLeafMethods +ART_GTEST_object_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY +ART_GTEST_proxy_test_DEX_DEPS := Interfaces +ART_GTEST_reflection_test_DEX_DEPS := Main NonStaticLeafMethods StaticLeafMethods +ART_GTEST_stub_test_DEX_DEPS := AllFields +ART_GTEST_transaction_test_DEX_DEPS := Transaction + +# The elf writer test has dependencies on core.oat. +ART_GTEST_elf_writer_test_HOST_DEPS := $(HOST_CORE_OAT_OUT) $(2ND_HOST_CORE_OAT_OUT) +ART_GTEST_elf_writer_test_TARGET_DEPS := $(TARGET_CORE_OAT_OUT) $(2ND_TARGET_CORE_OAT_OUT) + +# The path for which all the source files are relative, not actually the current directory. LOCAL_PATH := art -TEST_COMMON_SRC_FILES := \ - compiler/driver/compiler_driver_test.cc \ - compiler/elf_writer_test.cc \ - compiler/image_test.cc \ - compiler/jni/jni_compiler_test.cc \ - compiler/oat_test.cc \ - compiler/output_stream_test.cc \ - compiler/utils/dedupe_set_test.cc \ - compiler/utils/arm/managed_register_arm_test.cc \ - compiler/utils/x86/managed_register_x86_test.cc \ - runtime/barrier_test.cc \ - runtime/base/histogram_test.cc \ - runtime/base/mutex_test.cc \ - runtime/base/timing_logger_test.cc \ - runtime/base/unix_file/fd_file_test.cc \ - runtime/base/unix_file/mapped_file_test.cc \ - runtime/base/unix_file/null_file_test.cc \ - runtime/base/unix_file/random_access_file_utils_test.cc \ - runtime/base/unix_file/string_file_test.cc \ - runtime/class_linker_test.cc \ - runtime/dex_file_test.cc \ - runtime/dex_instruction_visitor_test.cc \ - runtime/dex_method_iterator_test.cc \ - runtime/entrypoints/math_entrypoints_test.cc \ - runtime/exception_test.cc \ - runtime/gc/accounting/space_bitmap_test.cc \ - runtime/gc/heap_test.cc \ - runtime/gc/space/space_test.cc \ - runtime/gtest_test.cc \ - runtime/indenter_test.cc \ - runtime/indirect_reference_table_test.cc \ - runtime/intern_table_test.cc \ - runtime/jni_internal_test.cc \ - runtime/mem_map_test.cc \ - runtime/mirror/dex_cache_test.cc \ - runtime/mirror/object_test.cc \ - runtime/reference_table_test.cc \ - runtime/runtime_test.cc \ - runtime/thread_pool_test.cc \ - runtime/utils_test.cc \ - runtime/verifier/method_verifier_test.cc \ - runtime/verifier/reg_type_test.cc \ - runtime/zip_archive_test.cc +RUNTIME_GTEST_COMMON_SRC_FILES := \ + runtime/arch/arch_test.cc \ + runtime/arch/memcmp16_test.cc \ + runtime/arch/stub_test.cc \ + runtime/barrier_test.cc \ + runtime/base/bit_field_test.cc \ + runtime/base/bit_vector_test.cc \ + runtime/base/hex_dump_test.cc \ + runtime/base/histogram_test.cc \ + runtime/base/mutex_test.cc \ + runtime/base/scoped_flock_test.cc \ + runtime/base/stringprintf_test.cc \ + runtime/base/timing_logger_test.cc \ + runtime/base/unix_file/fd_file_test.cc \ + runtime/base/unix_file/mapped_file_test.cc \ + runtime/base/unix_file/null_file_test.cc \ + runtime/base/unix_file/random_access_file_utils_test.cc \ + runtime/base/unix_file/string_file_test.cc \ + runtime/class_linker_test.cc \ + runtime/dex_file_test.cc \ + runtime/dex_file_verifier_test.cc \ + runtime/dex_instruction_visitor_test.cc \ + runtime/dex_method_iterator_test.cc \ + runtime/entrypoints/math_entrypoints_test.cc \ + runtime/entrypoints/quick/quick_trampoline_entrypoints_test.cc \ + runtime/entrypoints_order_test.cc \ + runtime/exception_test.cc \ + runtime/gc/accounting/card_table_test.cc \ + runtime/gc/accounting/space_bitmap_test.cc \ + runtime/gc/heap_test.cc \ + runtime/gc/space/dlmalloc_space_base_test.cc \ + runtime/gc/space/dlmalloc_space_static_test.cc \ + runtime/gc/space/dlmalloc_space_random_test.cc \ + runtime/gc/space/rosalloc_space_base_test.cc \ + runtime/gc/space/rosalloc_space_static_test.cc \ + runtime/gc/space/rosalloc_space_random_test.cc \ + runtime/gc/space/large_object_space_test.cc \ + runtime/gtest_test.cc \ + runtime/handle_scope_test.cc \ + runtime/indenter_test.cc \ + runtime/indirect_reference_table_test.cc \ + runtime/instruction_set_test.cc \ + runtime/intern_table_test.cc \ + runtime/leb128_test.cc \ + runtime/mem_map_test.cc \ + runtime/mirror/dex_cache_test.cc \ + runtime/mirror/object_test.cc \ + runtime/monitor_pool_test.cc \ + runtime/monitor_test.cc \ + runtime/parsed_options_test.cc \ + runtime/reference_table_test.cc \ + runtime/thread_pool_test.cc \ + runtime/transaction_test.cc \ + runtime/utils_test.cc \ + runtime/verifier/method_verifier_test.cc \ + runtime/verifier/reg_type_test.cc \ + runtime/zip_archive_test.cc + +COMPILER_GTEST_COMMON_SRC_FILES := \ + runtime/jni_internal_test.cc \ + runtime/proxy_test.cc \ + runtime/reflection_test.cc \ + compiler/dex/global_value_numbering_test.cc \ + compiler/dex/local_value_numbering_test.cc \ + compiler/dex/mir_graph_test.cc \ + compiler/dex/mir_optimization_test.cc \ + compiler/driver/compiler_driver_test.cc \ + compiler/elf_writer_test.cc \ + compiler/image_test.cc \ + compiler/jni/jni_compiler_test.cc \ + compiler/oat_test.cc \ + compiler/optimizing/codegen_test.cc \ + compiler/optimizing/dominator_test.cc \ + compiler/optimizing/find_loops_test.cc \ + compiler/optimizing/graph_test.cc \ + compiler/optimizing/linearize_test.cc \ + compiler/optimizing/liveness_test.cc \ + compiler/optimizing/live_interval_test.cc \ + compiler/optimizing/live_ranges_test.cc \ + compiler/optimizing/parallel_move_test.cc \ + compiler/optimizing/pretty_printer_test.cc \ + compiler/optimizing/register_allocator_test.cc \ + compiler/optimizing/ssa_test.cc \ + compiler/optimizing/stack_map_test.cc \ + compiler/output_stream_test.cc \ + compiler/utils/arena_allocator_test.cc \ + compiler/utils/dedupe_set_test.cc \ + compiler/utils/arm/managed_register_arm_test.cc \ + compiler/utils/arm64/managed_register_arm64_test.cc \ + compiler/utils/x86/managed_register_x86_test.cc \ ifeq ($(ART_SEA_IR_MODE),true) -TEST_COMMON_SRC_FILES += \ - compiler/utils/scoped_hashtable_test.cc \ - compiler/sea_ir/types/type_data_test.cc \ - compiler/sea_ir/types/type_inference_visitor_test.cc \ - compiler/sea_ir/ir/regions_test.cc +COMPILER_GTEST_COMMON_SRC_FILES += \ + compiler/utils/scoped_hashtable_test.cc \ + compiler/sea_ir/types/type_data_test.cc \ + compiler/sea_ir/types/type_inference_visitor_test.cc \ + compiler/sea_ir/ir/regions_test.cc endif -TEST_TARGET_SRC_FILES := \ - $(TEST_COMMON_SRC_FILES) +RUNTIME_GTEST_TARGET_SRC_FILES := \ + $(RUNTIME_GTEST_COMMON_SRC_FILES) -TEST_HOST_SRC_FILES := \ - $(TEST_COMMON_SRC_FILES) \ - compiler/utils/x86/assembler_x86_test.cc +RUNTIME_GTEST_HOST_SRC_FILES := \ + $(RUNTIME_GTEST_COMMON_SRC_FILES) -ART_HOST_TEST_EXECUTABLES := -ART_TARGET_TEST_EXECUTABLES := -ART_HOST_TEST_TARGETS := -ART_TARGET_TEST_TARGETS := +COMPILER_GTEST_TARGET_SRC_FILES := \ + $(COMPILER_GTEST_COMMON_SRC_FILES) + +COMPILER_GTEST_HOST_SRC_FILES := \ + $(COMPILER_GTEST_COMMON_SRC_FILES) \ + compiler/utils//assembler_thumb_test.cc \ + compiler/utils/x86/assembler_x86_test.cc \ + compiler/utils/x86_64/assembler_x86_64_test.cc ART_TEST_CFLAGS := ifeq ($(ART_USE_PORTABLE_COMPILER),true) ART_TEST_CFLAGS += -DART_USE_PORTABLE_COMPILER=1 endif +include $(CLEAR_VARS) +LOCAL_MODULE := libart-gtest +LOCAL_MODULE_TAGS := optional +LOCAL_CPP_EXTENSION := cc +LOCAL_CFLAGS := $(ART_TARGET_CFLAGS) +LOCAL_SRC_FILES := runtime/common_runtime_test.cc compiler/common_compiler_test.cc +LOCAL_C_INCLUDES := $(ART_C_INCLUDES) art/runtime art/compiler +LOCAL_SHARED_LIBRARIES := libcutils libartd libartd-compiler libdl +LOCAL_STATIC_LIBRARIES += libgtest_libc++ +LOCAL_CLANG := $(ART_TARGET_CLANG) +LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common_build.mk +LOCAL_ADDITIONAL_DEPENDENCIES += art/build/Android.gtest.mk +include external/libcxx/libcxx.mk +include $(BUILD_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := libart-gtest +LOCAL_MODULE_TAGS := optional +LOCAL_CPP_EXTENSION := cc +LOCAL_CFLAGS := $(ART_HOST_CFLAGS) +LOCAL_SRC_FILES := runtime/common_runtime_test.cc compiler/common_compiler_test.cc +LOCAL_C_INCLUDES := $(ART_C_INCLUDES) art/runtime art/compiler +LOCAL_SHARED_LIBRARIES := libartd libartd-compiler +LOCAL_STATIC_LIBRARIES := libcutils +ifneq ($(WITHOUT_HOST_CLANG),true) + # GCC host compiled tests fail with this linked, presumably due to destructors that run. + LOCAL_STATIC_LIBRARIES += libgtest_libc++_host +endif +LOCAL_LDLIBS += -ldl -lpthread +LOCAL_MULTILIB := both +LOCAL_CLANG := $(ART_HOST_CLANG) +LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common_build.mk +LOCAL_ADDITIONAL_DEPENDENCIES += art/build/Android.gtest.mk +include external/libcxx/libcxx.mk +include $(BUILD_HOST_SHARED_LIBRARY) + +# Variables holding collections of gtest pre-requisits used to run a number of gtests. +ART_TEST_HOST_GTEST$(ART_PHONY_TEST_HOST_SUFFIX)_RULES := +ART_TEST_HOST_GTEST$(2ND_ART_PHONY_TEST_HOST_SUFFIX)_RULES := +ART_TEST_HOST_GTEST_RULES := +ART_TEST_HOST_VALGRIND_GTEST$(ART_PHONY_TEST_HOST_SUFFIX)_RULES := +ART_TEST_HOST_VALGRIND_GTEST$(2ND_ART_PHONY_TEST_HOST_SUFFIX)_RULES := +ART_TEST_HOST_VALGRIND_GTEST_RULES := +ART_TEST_TARGET_GTEST$(ART_PHONY_TEST_TARGET_SUFFIX)_RULES := +ART_TEST_TARGET_GTEST$(2ND_ART_PHONY_TEST_TARGET_SUFFIX)_RULES := +ART_TEST_TARGET_GTEST_RULES := + +# Define a make rule for a target device gtest. +# $(1): gtest name - the name of the test we're building such as leb128_test. +# $(2): 2ND_ or undefined - used to differentiate between the primary and secondary architecture. +define define-art-gtest-rule-target + gtest_rule := test-art-target-gtest-$(1)$$($(2)ART_PHONY_TEST_TARGET_SUFFIX) + + # Add the test dependencies to test-art-target-sync, which will be a prerequisite for the test + # to ensure files are pushed to the device. + TEST_ART_TARGET_SYNC_DEPS += \ + $$(ART_GTEST_$(1)_TARGET_DEPS) \ + $(foreach file,$(ART_GTEST_$(1)_DEX_DEPS),$(ART_TEST_TARGET_GTEST_$(file)_DEX)) \ + $$(ART_TARGET_NATIVETEST_OUT)/$$(TARGET_$(2)ARCH)/$(1) \ + $$($(2)TARGET_OUT_SHARED_LIBRARIES)/libjavacore.so + +.PHONY: $$(gtest_rule) +$$(gtest_rule): test-art-target-sync + $(hide) adb shell touch $(ART_TARGET_TEST_DIR)/$(TARGET_$(2)ARCH)/$$@-$$$$PPID + $(hide) adb shell rm $(ART_TARGET_TEST_DIR)/$(TARGET_$(2)ARCH)/$$@-$$$$PPID + $(hide) adb shell chmod 755 $(ART_TARGET_NATIVETEST_DIR)/$(TARGET_$(2)ARCH)/$(1) + $(hide) $$(call ART_TEST_SKIP,$$@) && \ + (adb shell "$(ART_TARGET_NATIVETEST_DIR)/$(TARGET_$(2)ARCH)/$(1) && touch $(ART_TARGET_TEST_DIR)/$(TARGET_$(2)ARCH)/$$@-$$$$PPID" \ + && (adb pull $(ART_TARGET_TEST_DIR)/$(TARGET_$(2)ARCH)/$$@-$$$$PPID /tmp/ \ + && $$(call ART_TEST_PASSED,$$@)) \ + || $$(call ART_TEST_FAILED,$$@)) + $(hide) rm /tmp/$$@-$$$$PPID + + ART_TEST_TARGET_GTEST$($(2)ART_PHONY_TEST_TARGET_SUFFIX)_RULES += $$(gtest_rule) + ART_TEST_TARGET_GTEST_RULES += $$(gtest_rule) + ART_TEST_TARGET_GTEST_$(1)_RULES += $$(gtest_rule) + + # Clear locally defined variables. + gtest_rule := +endef # define-art-gtest-rule-target + +# Define make rules for a host gtests. +# $(1): gtest name - the name of the test we're building such as leb128_test. +# $(2): 2ND_ or undefined - used to differentiate between the primary and secondary architecture. +define define-art-gtest-rule-host + gtest_rule := test-art-host-gtest-$(1)$$($(2)ART_PHONY_TEST_HOST_SUFFIX) + gtest_exe := $$(HOST_OUT_EXECUTABLES)/$(1)$$($(2)ART_PHONY_TEST_HOST_SUFFIX) + # Dependencies for all host gtests. + gtest_deps := $$(HOST_CORE_DEX_LOCATIONS) \ + $$($(2)ART_HOST_OUT_SHARED_LIBRARIES)/libjavacore$$(ART_HOST_SHLIB_EXTENSION) + + +.PHONY: $$(gtest_rule) +$$(gtest_rule): $$(gtest_exe) $$(ART_GTEST_$(1)_HOST_DEPS) $(foreach file,$(ART_GTEST_$(1)_DEX_DEPS),$(ART_TEST_HOST_GTEST_$(file)_DEX)) $$(gtest_deps) + $(hide) ($$(call ART_TEST_SKIP,$$@) && LD_PRELOAD=libsigchain$$(ART_HOST_SHLIB_EXTENSION) $$< && $$(call ART_TEST_PASSED,$$@)) \ + || $$(call ART_TEST_FAILED,$$@) + + ART_TEST_HOST_GTEST$$($(2)ART_PHONY_TEST_HOST_SUFFIX)_RULES += $$(gtest_rule) + ART_TEST_HOST_GTEST_RULES += $$(gtest_rule) + ART_TEST_HOST_GTEST_$(1)_RULES += $$(gtest_rule) + +.PHONY: valgrind-$$(gtest_rule) +valgrind-$$(gtest_rule): $$(gtest_exe) $$(ART_GTEST_$(1)_HOST_DEPS) $(foreach file,$(ART_GTEST_$(1)_DEX_DEPS),$(ART_TEST_HOST_GTEST_$(file)_DEX)) $$(gtest_deps) + $(hide) $$(call ART_TEST_SKIP,$$@) && \ + valgrind --leak-check=full --error-exitcode=1 $$< && $$(call ART_TEST_PASSED,$$@) \ + || $$(call ART_TEST_FAILED,$$@) + + ART_TEST_HOST_VALGRIND_GTEST$$($(2)ART_PHONY_TEST_HOST_SUFFIX)_RULES += valgrind-$$(gtest_rule) + ART_TEST_HOST_VALGRIND_GTEST_RULES += valgrind-$$(gtest_rule) + ART_TEST_HOST_VALGRIND_GTEST_$(1)_RULES += valgrind-$$(gtest_rule) + + # Clear locally defined variables. + valgrind_gtest_rule := + gtest_rule := + gtest_exe := + gtest_deps := +endef # define-art-gtest-rule-host + +# Define the rules to build and run host and target gtests. # $(1): target or host # $(2): file name -define build-art-test +# $(3): extra C includes +# $(4): extra shared libraries +define define-art-gtest ifneq ($(1),target) ifneq ($(1),host) $$(error expected target or host for argument 1, received $(1)) @@ -96,86 +316,190 @@ define build-art-test art_target_or_host := $(1) art_gtest_filename := $(2) + art_gtest_extra_c_includes := $(3) + art_gtest_extra_shared_libraries := $(4) + include $$(CLEAR_VARS) 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) runtime/common_test.cc - LOCAL_C_INCLUDES += $(ART_C_INCLUDES) art/runtime art/compiler - LOCAL_SHARED_LIBRARIES += libartd-compiler libartd - # dex2oatd is needed to go libartd-compiler and libartd - LOCAL_REQUIRED_MODULES := dex2oatd + LOCAL_CPP_EXTENSION := $$(ART_CPP_EXTENSION) + LOCAL_SRC_FILES := $$(art_gtest_filename) + LOCAL_C_INCLUDES += $$(ART_C_INCLUDES) art/runtime $$(art_gtest_extra_c_includes) + LOCAL_SHARED_LIBRARIES += libartd $$(art_gtest_extra_shared_libraries) libart-gtest - LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common.mk + LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common_build.mk LOCAL_ADDITIONAL_DEPENDENCIES += art/build/Android.gtest.mk # Mac OS linker doesn't understand --export-dynamic. - ifneq ($(HOST_OS)-$$(art_target_or_host),darwin-host) + 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) + LOCAL_CFLAGS := $$(ART_TEST_CFLAGS) + include external/libcxx/libcxx.mk 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 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) + $$(eval $$(call set-target-local-clang-vars)) + $$(eval $$(call set-target-local-cflags-vars,debug)) + LOCAL_SHARED_LIBRARIES += libdl libicuuc libicui18n libnativehelper libz libcutils libvixl + LOCAL_MODULE_PATH_32 := $$(ART_TARGET_NATIVETEST_OUT)/$$(ART_TARGET_ARCH_32) + LOCAL_MODULE_PATH_64 := $$(ART_TARGET_NATIVETEST_OUT)/$$(ART_TARGET_ARCH_64) + LOCAL_MULTILIB := both + include $$(BUILD_EXECUTABLE) + + ART_TEST_TARGET_GTEST_$$(art_gtest_name)_RULES := + ifdef TARGET_2ND_ARCH + $$(eval $$(call define-art-gtest-rule-target,$$(art_gtest_name),2ND_)) + endif + $$(eval $$(call define-art-gtest-rule-target,$$(art_gtest_name),)) + + # A rule to run the different architecture versions of the gtest. +.PHONY: test-art-target-gtest-$$(art_gtest_name) +test-art-target-gtest-$$(art_gtest_name): $$(ART_TEST_TARGET_GTEST_$$(art_gtest_name)_RULES) + $$(hide) $$(call ART_TEST_PREREQ_FINISHED,$$@) + + # Clear locally defined variables. + ART_TEST_TARGET_GTEST_$$(art_gtest_name)_RULES := else # host - LOCAL_CLANG := $(ART_HOST_CLANG) - LOCAL_CFLAGS += $(ART_HOST_CFLAGS) $(ART_HOST_DEBUG_CFLAGS) + 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 + LOCAL_STATIC_LIBRARIES += libcutils libvixl + LOCAL_LDLIBS += -lpthread -ldl + LOCAL_IS_HOST_MODULE := true + LOCAL_MULTILIB := both + LOCAL_MODULE_STEM_32 := $$(art_gtest_name)32 + LOCAL_MODULE_STEM_64 := $$(art_gtest_name)64 + include $$(BUILD_HOST_EXECUTABLE) + + ART_TEST_HOST_GTEST_$$(art_gtest_name)_RULES := + ART_TEST_HOST_VALGRIND_GTEST_$$(art_gtest_name)_RULES := + ifneq ($$(HOST_PREFER_32_BIT),true) + $$(eval $$(call define-art-gtest-rule-host,$$(art_gtest_name),2ND_)) 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 + $$(eval $$(call define-art-gtest-rule-host,$$(art_gtest_name),)) + + # Rules to run the different architecture versions of the gtest. +.PHONY: test-art-host-gtest-$$(art_gtest_name) +test-art-host-gtest-$$(art_gtest_name): $$(ART_TEST_HOST_GTEST_$$(art_gtest_name)_RULES) + $$(hide) $$(call ART_TEST_PREREQ_FINISHED,$$@) + +.PHONY: valgrind-test-art-host-gtest-$$(art_gtest_name) +valgrind-test-art-host-gtest-$$(art_gtest_name): $$(ART_TEST_HOST_VALGRIND_GTEST_$$(art_gtest_name)_RULES) + $$(hide) $$(call ART_TEST_PREREQ_FINISHED,$$@) + + # Clear locally defined variables. + ART_TEST_HOST_GTEST_$$(art_gtest_name)_RULES := + ART_TEST_HOST_VALGRIND_GTEST_$$(art_gtest_name)_RULES := + endif # host_or_target + + # Clear locally defined variables. + art_target_or_host := + art_gtest_filename := + art_gtest_extra_c_includes := + art_gtest_extra_shared_libraries := + art_gtest_name := +endef # define-art-gtest ifeq ($(ART_BUILD_TARGET),true) - $(foreach file,$(TEST_TARGET_SRC_FILES), $(eval $(call build-art-test,target,$(file)))) + $(foreach file,$(RUNTIME_GTEST_TARGET_SRC_FILES), $(eval $(call define-art-gtest,target,$(file),,))) + $(foreach file,$(COMPILER_GTEST_TARGET_SRC_FILES), $(eval $(call define-art-gtest,target,$(file),art/compiler,libartd-compiler))) endif -ifeq ($(WITH_HOST_DALVIK),true) - ifeq ($(ART_BUILD_HOST),true) - $(foreach file,$(TEST_HOST_SRC_FILES), $(eval $(call build-art-test,host,$(file)))) +ifeq ($(ART_BUILD_HOST),true) + $(foreach file,$(RUNTIME_GTEST_HOST_SRC_FILES), $(eval $(call define-art-gtest,host,$(file),,))) + $(foreach file,$(COMPILER_GTEST_HOST_SRC_FILES), $(eval $(call define-art-gtest,host,$(file),art/compiler,libartd-compiler))) +endif + +# Used outside the art project to get a list of the current tests +RUNTIME_TARGET_GTEST_MAKE_TARGETS := +$(foreach file, $(RUNTIME_GTEST_TARGET_SRC_FILES), $(eval RUNTIME_TARGET_GTEST_MAKE_TARGETS += $$(notdir $$(basename $$(file))))) +COMPILER_TARGET_GTEST_MAKE_TARGETS := +$(foreach file, $(COMPILER_GTEST_TARGET_SRC_FILES), $(eval COMPILER_TARGET_GTEST_MAKE_TARGETS += $$(notdir $$(basename $$(file))))) + +# Define all the combinations of host/target, valgrind and suffix such as: +# test-art-host-gtest or valgrind-test-art-host-gtest64 +# $(1): host or target +# $(2): HOST or TARGET +# $(3): valgrind- or undefined +# $(4): undefined, 32 or 64 +define define-test-art-gtest-combination + ifeq ($(1),host) + ifneq ($(2),HOST) + $$(error argument mismatch $(1) and ($2)) + endif + else + ifneq ($(1),target) + $$(error found $(1) expected host or target) + endif + ifneq ($(2),TARGET) + $$(error argument mismatch $(1) and ($2)) + endif endif + + rule_name := $(3)test-art-$(1)-gtest$(4) + dependencies := $$(ART_TEST_$(2)_GTEST$(4)_RULES) + +.PHONY: $$(rule_name) +$$(rule_name): $$(dependencies) + $(hide) $$(call ART_TEST_PREREQ_FINISHED,$$@) + + # Clear locally defined variables. + rule_name := + dependencies := +endef # define-test-art-gtest-combination + +$(eval $(call define-test-art-gtest-combination,target,TARGET,,)) +$(eval $(call define-test-art-gtest-combination,target,TARGET,,$(ART_PHONY_TEST_TARGET_SUFFIX))) +ifdef TARGET_2ND_ARCH +$(eval $(call define-test-art-gtest-combination,target,TARGET,,$(2ND_ART_PHONY_TEST_TARGET_SUFFIX))) endif +$(eval $(call define-test-art-gtest-combination,host,HOST,,)) +$(eval $(call define-test-art-gtest-combination,host,HOST,valgrind-,)) +$(eval $(call define-test-art-gtest-combination,host,HOST,,$(ART_PHONY_TEST_HOST_SUFFIX))) +$(eval $(call define-test-art-gtest-combination,host,HOST,valgrind-,$(ART_PHONY_TEST_HOST_SUFFIX))) +ifneq ($(HOST_PREFER_32_BIT),true) +$(eval $(call define-test-art-gtest-combination,host,HOST,,$(2ND_ART_PHONY_TEST_HOST_SUFFIX))) +$(eval $(call define-test-art-gtest-combination,host,HOST,valgrind-,$(2ND_ART_PHONY_TEST_HOST_SUFFIX))) +endif + +# Clear locally defined variables. +define-art-gtest-rule-target := +define-art-gtest-rule-host := +define-art-gtest := +define-test-art-gtest-combination := +RUNTIME_GTEST_COMMON_SRC_FILES := +COMPILER_GTEST_COMMON_SRC_FILES := +RUNTIME_GTEST_TARGET_SRC_FILES := +RUNTIME_GTEST_HOST_SRC_FILES := +COMPILER_GTEST_TARGET_SRC_FILES := +COMPILER_GTEST_HOST_SRC_FILES := +ART_TEST_CFLAGS := +ART_TEST_HOST_GTEST$(ART_PHONY_TEST_HOST_SUFFIX)_RULES := +ART_TEST_HOST_GTEST$(2ND_ART_PHONY_TEST_HOST_SUFFIX)_RULES := +ART_TEST_HOST_GTEST_RULES := +ART_TEST_HOST_VALGRIND_GTEST$(ART_PHONY_TEST_HOST_SUFFIX)_RULES := +ART_TEST_HOST_VALGRIND_GTEST$(2ND_ART_PHONY_TEST_HOST_SUFFIX)_RULES := +ART_TEST_HOST_VALGRIND_GTEST_RULES := +ART_TEST_TARGET_GTEST$(ART_PHONY_TEST_TARGET_SUFFIX)_RULES := +ART_TEST_TARGET_GTEST$(2ND_ART_PHONY_TEST_TARGET_SUFFIX)_RULES := +ART_TEST_TARGET_GTEST_RULES := +ART_GTEST_class_linker_test_DEX_DEPS := +ART_GTEST_compiler_driver_test_DEX_DEPS := +ART_GTEST_dex_file_test_DEX_DEPS := +ART_GTEST_exception_test_DEX_DEPS := +ART_GTEST_elf_writer_test_HOST_DEPS := +ART_GTEST_elf_writer_test_TARGET_DEPS := +ART_GTEST_jni_compiler_test_DEX_DEPS := +ART_GTEST_jni_internal_test_DEX_DEPS := +ART_GTEST_object_test_DEX_DEPS := +ART_GTEST_proxy_test_DEX_DEPS := +ART_GTEST_reflection_test_DEX_DEPS := +ART_GTEST_stub_test_DEX_DEPS := +ART_GTEST_transaction_test_DEX_DEPS := +$(foreach dir,$(GTEST_DEX_DIRECTORIES), $(eval ART_TEST_TARGET_GTEST_$(dir)_DEX :=)) +$(foreach dir,$(GTEST_DEX_DIRECTORIES), $(eval ART_TEST_HOST_GTEST_$(dir)_DEX :=)) +GTEST_DEX_DIRECTORIES := +LOCAL_PATH := diff --git a/build/Android.oat.mk b/build/Android.oat.mk index ea7b0b08ca9..10936a45d67 100644 --- a/build/Android.oat.mk +++ b/build/Android.oat.mk @@ -14,62 +14,63 @@ # 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-libart 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 +# Rules to build a smaller "core" image to support core libraries +# (that is, non-Android frameworks) testing on the host and target +# +# The main rules to build the default "boot" image are in +# build/core/dex_preopt_libart.mk + +include art/build/Android.common_path.mk + +# Use dex2oat debug version for better error reporting +# $(1): 2ND_ or undefined, 2ND_ for 32-bit host builds. +define create-core-oat-host-rules +$$($(1)HOST_CORE_IMG_OUT): $$(HOST_CORE_DEX_FILES) $$(DEX2OATD_DEPENDENCY) + @echo "host dex2oat: $$@ ($$?)" + @mkdir -p $$(dir $$@) + $$(hide) $$(DEX2OATD) --runtime-arg -Xms$(DEX2OAT_IMAGE_XMS) --runtime-arg -Xmx$(DEX2OAT_IMAGE_XMX) \ + --image-classes=$$(PRELOADED_CLASSES) $$(addprefix --dex-file=,$$(HOST_CORE_DEX_FILES)) \ + $$(addprefix --dex-location=,$$(HOST_CORE_DEX_LOCATIONS)) --oat-file=$$($(1)HOST_CORE_OAT_OUT) \ + --oat-location=$$($(1)HOST_CORE_OAT) --image=$$($(1)HOST_CORE_IMG_OUT) \ + --base=$$(LIBART_IMG_HOST_BASE_ADDRESS) --instruction-set=$$($(1)ART_HOST_ARCH) \ + --instruction-set-features=$$($(1)HOST_INSTRUCTION_SET_FEATURES) \ + --host --android-root=$$(HOST_OUT) --include-patch-information + +# This "renaming" eases declaration in art/Android.mk +HOST_CORE_IMG_OUT$($(1)ART_PHONY_TEST_HOST_SUFFIX) := $($(1)HOST_CORE_IMG_OUT) + +$$($(1)HOST_CORE_OAT_OUT): $$($(1)HOST_CORE_IMG_OUT) +endef # create-core-oat-host-rules + +$(eval $(call create-core-oat-host-rules,)) +ifneq ($(HOST_PREFER_32_BIT),true) +$(eval $(call create-core-oat-host-rules,2ND_)) +endif -$(HOST_CORE_OAT_OUT): $(HOST_CORE_IMG_OUT) +define create-core-oat-target-rules +$$($(1)TARGET_CORE_IMG_OUT): $$($(1)TARGET_CORE_DEX_FILES) $$(DEX2OATD_DEPENDENCY) + @echo "target dex2oat: $$@ ($$?)" + @mkdir -p $$(dir $$@) + $$(hide) $$(DEX2OATD) --runtime-arg -Xms$(DEX2OAT_XMS) --runtime-arg -Xmx$(DEX2OAT_XMX) \ + --image-classes=$$(PRELOADED_CLASSES) $$(addprefix --dex-file=,$$(TARGET_CORE_DEX_FILES)) \ + $$(addprefix --dex-location=,$$(TARGET_CORE_DEX_LOCATIONS)) --oat-file=$$($(1)TARGET_CORE_OAT_OUT) \ + --oat-location=$$($(1)TARGET_CORE_OAT) --image=$$($(1)TARGET_CORE_IMG_OUT) \ + --base=$$(LIBART_IMG_TARGET_BASE_ADDRESS) --instruction-set=$$($(1)TARGET_ARCH) \ + --instruction-set-features=$$($(1)TARGET_INSTRUCTION_SET_FEATURES) \ + --android-root=$$(PRODUCT_OUT)/system --include-patch-information + +# This "renaming" eases declaration in art/Android.mk +TARGET_CORE_IMG_OUT$($(1)ART_PHONY_TEST_TARGET_SUFFIX) := $($(1)TARGET_CORE_IMG_OUT) + +$$($(1)TARGET_CORE_OAT_OUT): $$($(1)TARGET_CORE_IMG_OUT) +endef # create-core-oat-target-rules + +ifdef TARGET_2ND_ARCH +$(eval $(call create-core-oat-target-rules,2ND_)) +endif +$(eval $(call create-core-oat-target-rules,)) -$(TARGET_CORE_OAT_OUT): $(TARGET_CORE_IMG_OUT) ifeq ($(ART_BUILD_HOST),true) include $(CLEAR_VARS) @@ -79,36 +80,15 @@ LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common.mk LOCAL_ADDITIONAL_DEPENDENCIES += art/build/Android.oat.mk 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_JARS := $(foreach jar,$(TARGET_BOOT_JARS),$(patsubst core, core-libart,$(jar))) -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 +endif # ART_BUILD_HOST -$(TARGET_BOOT_OAT_UNSTRIPPED_OUT): $(TARGET_BOOT_IMG_OUT) - -$(TARGET_BOOT_OAT_OUT): $(TARGET_BOOT_OAT_UNSTRIPPED_OUT) - -ifeq ($(ART_BUILD_TARGET_NDEBUG),true) +# If we aren't building the host toolchain, skip building the target core.art. +ifeq ($(ART_BUILD_TARGET),true) include $(CLEAR_VARS) -LOCAL_MODULE := boot.art +LOCAL_MODULE := core.art LOCAL_MODULE_TAGS := optional LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common.mk LOCAL_ADDITIONAL_DEPENDENCIES += art/build/Android.oat.mk -LOCAL_ADDITIONAL_DEPENDENCIES += $(TARGET_BOOT_IMG_OUT) $(TARGET_BOOT_OAT_OUT) +LOCAL_ADDITIONAL_DEPENDENCIES += $(TARGET_CORE_IMG_OUT) include $(BUILD_PHONY_PACKAGE) -endif +endif # ART_BUILD_TARGET diff --git a/compiler/Android.mk b/compiler/Android.mk index 66ff46163ba..c0867fd40f0 100644 --- a/compiler/Android.mk +++ b/compiler/Android.mk @@ -16,20 +16,27 @@ LOCAL_PATH := $(call my-dir) -include art/build/Android.common.mk +include art/build/Android.common_build.mk LIBART_COMPILER_SRC_FILES := \ compiled_method.cc \ + dex/global_value_numbering.cc \ dex/local_value_numbering.cc \ - dex/arena_allocator.cc \ - dex/arena_bit_vector.cc \ dex/quick/arm/assemble_arm.cc \ dex/quick/arm/call_arm.cc \ dex/quick/arm/fp_arm.cc \ dex/quick/arm/int_arm.cc \ dex/quick/arm/target_arm.cc \ dex/quick/arm/utility_arm.cc \ + dex/quick/arm64/assemble_arm64.cc \ + dex/quick/arm64/call_arm64.cc \ + dex/quick/arm64/fp_arm64.cc \ + dex/quick/arm64/int_arm64.cc \ + dex/quick/arm64/target_arm64.cc \ + dex/quick/arm64/utility_arm64.cc \ dex/quick/codegen_util.cc \ + dex/quick/dex_file_method_inliner.cc \ + dex/quick/dex_file_to_method_inliner_map.cc \ dex/quick/gen_common.cc \ dex/quick/gen_invoke.cc \ dex/quick/gen_loadstore.cc \ @@ -42,50 +49,76 @@ LIBART_COMPILER_SRC_FILES := \ dex/quick/mips/utility_mips.cc \ dex/quick/mir_to_lir.cc \ dex/quick/ralloc_util.cc \ + dex/quick/resource_mask.cc \ dex/quick/x86/assemble_x86.cc \ dex/quick/x86/call_x86.cc \ dex/quick/x86/fp_x86.cc \ dex/quick/x86/int_x86.cc \ dex/quick/x86/target_x86.cc \ dex/quick/x86/utility_x86.cc \ - dex/portable/mir_to_gbc.cc \ dex/dex_to_dex_compiler.cc \ dex/mir_dataflow.cc \ + dex/mir_field_info.cc \ + dex/mir_method_info.cc \ dex/mir_optimization.cc \ + dex/bb_optimizations.cc \ + dex/post_opt_passes.cc \ + dex/pass_driver_me_opts.cc \ + dex/pass_driver_me_post_opt.cc \ dex/frontend.cc \ dex/mir_graph.cc \ dex/mir_analysis.cc \ + dex/verified_method.cc \ + dex/verification_results.cc \ dex/vreg_analysis.cc \ dex/ssa_transformation.cc \ + dex/quick_compiler_callbacks.cc \ driver/compiler_driver.cc \ driver/dex_compilation_unit.cc \ - jni/portable/jni_compiler.cc \ jni/quick/arm/calling_convention_arm.cc \ + jni/quick/arm64/calling_convention_arm64.cc \ jni/quick/mips/calling_convention_mips.cc \ jni/quick/x86/calling_convention_x86.cc \ + jni/quick/x86_64/calling_convention_x86_64.cc \ jni/quick/calling_convention.cc \ jni/quick/jni_compiler.cc \ - llvm/compiler_llvm.cc \ - llvm/gbc_expander.cc \ - llvm/generated/art_module.cc \ - llvm/intrinsic_helper.cc \ - llvm/ir_builder.cc \ - llvm/llvm_compilation_unit.cc \ - llvm/md_builder.cc \ - llvm/runtime_support_builder.cc \ - llvm/runtime_support_builder_arm.cc \ - llvm/runtime_support_builder_thumb2.cc \ - llvm/runtime_support_builder_x86.cc \ + optimizing/builder.cc \ + optimizing/code_generator.cc \ + optimizing/code_generator_arm.cc \ + optimizing/code_generator_x86.cc \ + optimizing/code_generator_x86_64.cc \ + optimizing/graph_visualizer.cc \ + optimizing/locations.cc \ + optimizing/nodes.cc \ + optimizing/optimizing_compiler.cc \ + optimizing/parallel_move_resolver.cc \ + optimizing/register_allocator.cc \ + optimizing/ssa_builder.cc \ + optimizing/ssa_liveness_analysis.cc \ + optimizing/ssa_phi_elimination.cc \ + optimizing/ssa_type_propagation.cc \ trampolines/trampoline_compiler.cc \ + utils/arena_allocator.cc \ + utils/arena_bit_vector.cc \ utils/arm/assembler_arm.cc \ + utils/arm/assembler_arm32.cc \ + utils/arm/assembler_thumb2.cc \ utils/arm/managed_register_arm.cc \ + utils/arm64/assembler_arm64.cc \ + utils/arm64/managed_register_arm64.cc \ utils/assembler.cc \ utils/mips/assembler_mips.cc \ utils/mips/managed_register_mips.cc \ utils/x86/assembler_x86.cc \ utils/x86/managed_register_x86.cc \ + utils/x86_64/assembler_x86_64.cc \ + utils/x86_64/managed_register_x86_64.cc \ + utils/scoped_arena_allocator.cc \ buffered_output_stream.cc \ + compilers.cc \ + compiler.cc \ elf_fixup.cc \ + elf_patcher.cc \ elf_stripper.cc \ elf_writer.cc \ elf_writer_quick.cc \ @@ -107,9 +140,23 @@ LIBART_COMPILER_SRC_FILES += \ endif LIBART_COMPILER_CFLAGS := + ifeq ($(ART_USE_PORTABLE_COMPILER),true) - LIBART_COMPILER_SRC_FILES += elf_writer_mclinker.cc - LIBART_COMPILER_CFLAGS += -DART_USE_PORTABLE_COMPILER=1 +LIBART_COMPILER_SRC_FILES += \ + dex/portable/mir_to_gbc.cc \ + elf_writer_mclinker.cc \ + jni/portable/jni_compiler.cc \ + llvm/compiler_llvm.cc \ + llvm/gbc_expander.cc \ + llvm/generated/art_module.cc \ + llvm/intrinsic_helper.cc \ + llvm/ir_builder.cc \ + llvm/llvm_compilation_unit.cc \ + llvm/md_builder.cc \ + llvm/runtime_support_builder.cc \ + llvm/runtime_support_builder_arm.cc \ + llvm/runtime_support_builder_x86.cc +LIBART_COMPILER_CFLAGS += -DART_USE_PORTABLE_COMPILER=1 endif LIBART_COMPILER_ENUM_OPERATOR_OUT_HEADER_FILES := \ @@ -133,16 +180,17 @@ define build-libart-compiler art_ndebug_or_debug := $(2) include $(CLEAR_VARS) - ifeq ($$(art_target_or_host),target) - include external/stlport/libstlport.mk - else + ifeq ($$(art_target_or_host),host) LOCAL_IS_HOST_MODULE := true endif LOCAL_CPP_EXTENSION := $(ART_CPP_EXTENSION) ifeq ($$(art_ndebug_or_debug),ndebug) LOCAL_MODULE := libart-compiler + LOCAL_SHARED_LIBRARIES += libart + LOCAL_FDO_SUPPORT := true else # debug LOCAL_MODULE := libartd-compiler + LOCAL_SHARED_LIBRARIES += libartd endif LOCAL_MODULE_TAGS := optional @@ -150,7 +198,7 @@ define build-libart-compiler LOCAL_SRC_FILES := $$(LIBART_COMPILER_SRC_FILES) - GENERATED_SRC_DIR := $$(call intermediates-dir-for,$$(LOCAL_MODULE_CLASS),$$(LOCAL_MODULE),$$(LOCAL_IS_HOST_MODULE),) + GENERATED_SRC_DIR := $$(call local-generated-sources-dir) ENUM_OPERATOR_OUT_CC_FILES := $$(patsubst %.h,%_operator_out.cc,$$(LIBART_COMPILER_ENUM_OPERATOR_OUT_HEADER_FILES)) ENUM_OPERATOR_OUT_GEN := $$(addprefix $$(GENERATED_SRC_DIR)/,$$(ENUM_OPERATOR_OUT_CC_FILES)) @@ -162,54 +210,61 @@ $$(ENUM_OPERATOR_OUT_GEN): $$(GENERATED_SRC_DIR)/%_operator_out.cc : $(LOCAL_PAT LOCAL_GENERATED_SOURCES += $$(ENUM_OPERATOR_OUT_GEN) LOCAL_CFLAGS := $$(LIBART_COMPILER_CFLAGS) + include external/libcxx/libcxx.mk ifeq ($$(art_target_or_host),target) - LOCAL_CLANG := $(ART_TARGET_CLANG) - LOCAL_CFLAGS += $(ART_TARGET_CFLAGS) + $(call set-target-local-clang-vars) + $(call set-target-local-cflags-vars,$(2)) 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 # 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_USE_PORTABLE_COMPILER),true) + LOCAL_SHARED_LIBRARIES += libLLVM + LOCAL_CFLAGS += -DART_USE_PORTABLE_COMPILER=1 ifeq ($$(art_target_or_host),target) - LOCAL_CFLAGS += $(ART_TARGET_NON_DEBUG_CFLAGS) + LOCAL_STATIC_LIBRARIES_arm += libmcldARMInfo libmcldARMTarget + LOCAL_STATIC_LIBRARIES_x86 += libmcldX86Info libmcldX86Target + LOCAL_STATIC_LIBRARIES_x86_64 += libmcldX86Info libmcldX86Target + LOCAL_STATIC_LIBRARIES_mips += libmcldMipsInfo libmcldMipsTarget + ifeq ($(TARGET_ARCH),arm64) + $$(info TODOAArch64: $$(LOCAL_PATH)/Android.mk Add Arm64 specific MCLinker libraries) + endif # TARGET_ARCH != arm64 + include $(LLVM_DEVICE_BUILD_MK) else # host - LOCAL_CFLAGS += $(ART_HOST_NON_DEBUG_CFLAGS) + LOCAL_STATIC_LIBRARIES += libmcldARMInfo libmcldARMTarget + LOCAL_STATIC_LIBRARIES += libmcldX86Info libmcldX86Target + LOCAL_STATIC_LIBRARIES += libmcldMipsInfo libmcldMipsTarget + include $(LLVM_HOST_BUILD_MK) endif - LOCAL_SHARED_LIBRARIES += libart - endif - LOCAL_SHARED_LIBRARIES += libbcc libbcinfo libLLVM - ifeq ($(ART_USE_PORTABLE_COMPILER),true) - LOCAL_CFLAGS += -DART_USE_PORTABLE_COMPILER=1 + LOCAL_STATIC_LIBRARIES += libmcldCore libmcldObject libmcldADT libmcldFragment libmcldTarget libmcldCodeGen libmcldLDVariant libmcldMC libmcldSupport libmcldLD libmcldScript + include $(LLVM_GEN_INTRINSICS_MK) endif LOCAL_C_INCLUDES += $(ART_C_INCLUDES) art/runtime ifeq ($$(art_target_or_host),host) - LOCAL_LDLIBS := -ldl -lpthread + LOCAL_LDLIBS += -ldl -lpthread endif - LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common.mk + LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common_build.mk LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk ifeq ($$(art_target_or_host),target) - LOCAL_SHARED_LIBRARIES += libcutils - include $(LLVM_GEN_INTRINSICS_MK) - include $(LLVM_DEVICE_BUILD_MK) + LOCAL_SHARED_LIBRARIES += libcutils libvixl + ifeq ($(TARGET_HAVE_QC_PERF),true) + LOCAL_WHOLE_STATIC_LIBRARIES += libqc-art-compiler + endif include $(BUILD_SHARED_LIBRARY) else # host - LOCAL_STATIC_LIBRARIES += libcutils - include $(LLVM_GEN_INTRINSICS_MK) - include $(LLVM_HOST_BUILD_MK) + LOCAL_STATIC_LIBRARIES += libcutils libvixl + LOCAL_MULTILIB := both include $(BUILD_HOST_SHARED_LIBRARY) endif @@ -229,21 +284,18 @@ $$(ENUM_OPERATOR_OUT_GEN): $$(GENERATED_SRC_DIR)/%_operator_out.cc : $(LOCAL_PAT endef +ifeq ($(ART_BUILD_HOST_NDEBUG),true) + $(eval $(call build-libart-compiler,host,ndebug)) +endif +ifeq ($(ART_BUILD_HOST_DEBUG),true) + $(eval $(call build-libart-compiler,host,debug)) +endif 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 -ifeq ($(WITH_HOST_DALVIK),true) - # 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 -endif # Rule to build /system/lib/libcompiler_rt.a # Usually static libraries are not installed on the device. @@ -251,6 +303,9 @@ 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)) +ifdef TARGET_2ND_ARCH +$(eval $(call copy-one-file, $(call intermediates-dir-for,STATIC_LIBRARIES,libcompiler_rt,,,t)/libcompiler_rt.a, $(2ND_TARGET_OUT_SHARED_LIBRARIES)/libcompiler_rt.a)) +endif $(DEX2OAT): $(TARGET_OUT_SHARED_LIBRARIES)/libcompiler_rt.a diff --git a/compiler/buffered_output_stream.cc b/compiler/buffered_output_stream.cc index 81a58f62841..0940a80cc19 100644 --- a/compiler/buffered_output_stream.cc +++ b/compiler/buffered_output_stream.cc @@ -23,7 +23,7 @@ namespace art { BufferedOutputStream::BufferedOutputStream(OutputStream* out) : OutputStream(out->GetLocation()), out_(out), used_(0) {} -bool BufferedOutputStream::WriteFully(const void* buffer, int64_t byte_count) { +bool BufferedOutputStream::WriteFully(const void* buffer, size_t byte_count) { if (byte_count > kBufferSize) { Flush(); return out_->WriteFully(buffer, byte_count); diff --git a/compiler/buffered_output_stream.h b/compiler/buffered_output_stream.h index 7d874fbc5c3..75a3f24c701 100644 --- a/compiler/buffered_output_stream.h +++ b/compiler/buffered_output_stream.h @@ -31,7 +31,7 @@ class BufferedOutputStream : public OutputStream { delete out_; } - virtual bool WriteFully(const void* buffer, int64_t byte_count); + virtual bool WriteFully(const void* buffer, size_t byte_count); virtual off_t Seek(off_t offset, Whence whence); diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc new file mode 100644 index 00000000000..86167ec9bf3 --- /dev/null +++ b/compiler/common_compiler_test.cc @@ -0,0 +1,421 @@ +/* + * 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 "common_compiler_test.h" + +#if defined(__arm__) +#include +#endif +#include + +#include "class_linker.h" +#include "compiled_method.h" +#include "dex/quick_compiler_callbacks.h" +#include "dex/verification_results.h" +#include "dex/quick/dex_file_to_method_inliner_map.h" +#include "driver/compiler_driver.h" +#include "entrypoints/entrypoint_utils.h" +#include "interpreter/interpreter.h" +#include "mirror/art_method.h" +#include "mirror/dex_cache.h" +#include "mirror/object-inl.h" +#include "scoped_thread_state_change.h" +#include "thread-inl.h" +#include "utils.h" + +namespace art { + +// Normally the ClassLinker supplies this. +extern "C" void art_quick_generic_jni_trampoline(mirror::ArtMethod*); + +#if defined(__arm__) +// A signal handler called when have an illegal instruction. We record the fact in +// a global boolean and then increment the PC in the signal context to return to +// the next instruction. We know the instruction is an sdiv (4 bytes long). +static void baddivideinst(int signo, siginfo *si, void *data) { + UNUSED(signo); + UNUSED(si); + struct ucontext *uc = (struct ucontext *)data; + struct sigcontext *sc = &uc->uc_mcontext; + sc->arm_r0 = 0; // set R0 to #0 to signal error + sc->arm_pc += 4; // skip offending instruction +} + +// This is in arch/arm/arm_sdiv.S. It does the following: +// mov r1,#1 +// sdiv r0,r1,r1 +// bx lr +// +// the result will be the value 1 if sdiv is supported. If it is not supported +// a SIGILL signal will be raised and the signal handler (baddivideinst) called. +// The signal handler sets r0 to #0 and then increments pc beyond the failed instruction. +// Thus if the instruction is not supported, the result of this function will be #0 + +extern "C" bool CheckForARMSDIVInstruction(); + +static InstructionSetFeatures GuessInstructionFeatures() { + InstructionSetFeatures f; + + // Uncomment this for processing of /proc/cpuinfo. + if (false) { + // Look in /proc/cpuinfo for features we need. Only use this when we can guarantee that + // the kernel puts the appropriate feature flags in here. Sometimes it doesn't. + std::ifstream in("/proc/cpuinfo"); + if (in) { + while (!in.eof()) { + std::string line; + std::getline(in, line); + if (!in.eof()) { + if (line.find("Features") != std::string::npos) { + if (line.find("idivt") != std::string::npos) { + f.SetHasDivideInstruction(true); + } + } + } + in.close(); + } + } else { + LOG(INFO) << "Failed to open /proc/cpuinfo"; + } + } + + // See if have a sdiv instruction. Register a signal handler and try to execute + // an sdiv instruction. If we get a SIGILL then it's not supported. We can't use + // the /proc/cpuinfo method for this because Krait devices don't always put the idivt + // feature in the list. + struct sigaction sa, osa; + sa.sa_flags = SA_ONSTACK | SA_RESTART | SA_SIGINFO; + sa.sa_sigaction = baddivideinst; + sigaction(SIGILL, &sa, &osa); + + if (CheckForARMSDIVInstruction()) { + f.SetHasDivideInstruction(true); + } + + // Restore the signal handler. + sigaction(SIGILL, &osa, nullptr); + + // Other feature guesses in here. + return f; +} +#endif + +// Given a set of instruction features from the build, parse it. The +// input 'str' is a comma separated list of feature names. Parse it and +// return the InstructionSetFeatures object. +static InstructionSetFeatures ParseFeatureList(std::string str) { + InstructionSetFeatures result; + typedef std::vector FeatureList; + FeatureList features; + Split(str, ',', features); + for (FeatureList::iterator i = features.begin(); i != features.end(); i++) { + std::string feature = Trim(*i); + if (feature == "default") { + // Nothing to do. + } else if (feature == "div") { + // Supports divide instruction. + result.SetHasDivideInstruction(true); + } else if (feature == "nodiv") { + // Turn off support for divide instruction. + result.SetHasDivideInstruction(false); + } else { + LOG(FATAL) << "Unknown instruction set feature: '" << feature << "'"; + } + } + // Others... + return result; +} + +CommonCompilerTest::CommonCompilerTest() {} +CommonCompilerTest::~CommonCompilerTest() {} + +OatFile::OatMethod CommonCompilerTest::CreateOatMethod(const void* code, const uint8_t* gc_map) { + CHECK(code != nullptr); + const byte* base; + uint32_t code_offset, gc_map_offset; + if (gc_map == nullptr) { + base = reinterpret_cast(code); // Base of data points at code. + base -= kPointerSize; // Move backward so that code_offset != 0. + code_offset = kPointerSize; + gc_map_offset = 0; + } else { + // TODO: 64bit support. + base = nullptr; // Base of data in oat file, ie 0. + code_offset = PointerToLowMemUInt32(code); + gc_map_offset = PointerToLowMemUInt32(gc_map); + } + return OatFile::OatMethod(base, code_offset, gc_map_offset); +} + +void CommonCompilerTest::MakeExecutable(mirror::ArtMethod* method) { + CHECK(method != nullptr); + + const CompiledMethod* compiled_method = nullptr; + if (!method->IsAbstract()) { + mirror::DexCache* dex_cache = method->GetDeclaringClass()->GetDexCache(); + const DexFile& dex_file = *dex_cache->GetDexFile(); + compiled_method = + compiler_driver_->GetCompiledMethod(MethodReference(&dex_file, + method->GetDexMethodIndex())); + } + if (compiled_method != nullptr) { + const std::vector* code = compiled_method->GetQuickCode(); + const void* code_ptr; + if (code != nullptr) { + uint32_t code_size = code->size(); + CHECK_NE(0u, code_size); + const std::vector& vmap_table = compiled_method->GetVmapTable(); + uint32_t vmap_table_offset = vmap_table.empty() ? 0u + : sizeof(OatQuickMethodHeader) + vmap_table.size(); + const std::vector& mapping_table = compiled_method->GetMappingTable(); + uint32_t mapping_table_offset = mapping_table.empty() ? 0u + : sizeof(OatQuickMethodHeader) + vmap_table.size() + mapping_table.size(); + OatQuickMethodHeader method_header(mapping_table_offset, vmap_table_offset, + compiled_method->GetFrameSizeInBytes(), + compiled_method->GetCoreSpillMask(), + compiled_method->GetFpSpillMask(), code_size); + + header_code_and_maps_chunks_.push_back(std::vector()); + std::vector* chunk = &header_code_and_maps_chunks_.back(); + size_t size = sizeof(method_header) + code_size + vmap_table.size() + mapping_table.size(); + size_t code_offset = compiled_method->AlignCode(size - code_size); + size_t padding = code_offset - (size - code_size); + chunk->reserve(padding + size); + chunk->resize(sizeof(method_header)); + memcpy(&(*chunk)[0], &method_header, sizeof(method_header)); + chunk->insert(chunk->begin(), vmap_table.begin(), vmap_table.end()); + chunk->insert(chunk->begin(), mapping_table.begin(), mapping_table.end()); + chunk->insert(chunk->begin(), padding, 0); + chunk->insert(chunk->end(), code->begin(), code->end()); + CHECK_EQ(padding + size, chunk->size()); + code_ptr = &(*chunk)[code_offset]; + } else { + code = compiled_method->GetPortableCode(); + code_ptr = &(*code)[0]; + } + MakeExecutable(code_ptr, code->size()); + const void* method_code = CompiledMethod::CodePointer(code_ptr, + compiled_method->GetInstructionSet()); + LOG(INFO) << "MakeExecutable " << PrettyMethod(method) << " code=" << method_code; + OatFile::OatMethod oat_method = CreateOatMethod(method_code, nullptr); + oat_method.LinkMethod(method); + method->SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge); + } else { + // No code? You must mean to go into the interpreter. + // Or the generic JNI... + if (!method->IsNative()) { +#if defined(ART_USE_PORTABLE_COMPILER) + const void* method_code = GetPortableToInterpreterBridge(); +#else + const void* method_code = GetQuickToInterpreterBridge(); +#endif + OatFile::OatMethod oat_method = CreateOatMethod(method_code, nullptr); + oat_method.LinkMethod(method); + method->SetEntryPointFromInterpreter(interpreter::artInterpreterToInterpreterBridge); + } else { + const void* method_code = reinterpret_cast(art_quick_generic_jni_trampoline); + + OatFile::OatMethod oat_method = CreateOatMethod(method_code, nullptr); + oat_method.LinkMethod(method); + method->SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge); + } + } + // Create bridges to transition between different kinds of compiled bridge. +#if defined(ART_USE_PORTABLE_COMPILER) + if (method->GetEntryPointFromPortableCompiledCode() == nullptr) { + method->SetEntryPointFromPortableCompiledCode(GetPortableToQuickBridge()); + } else { + CHECK(method->GetEntryPointFromQuickCompiledCode() == nullptr); + method->SetEntryPointFromQuickCompiledCode(GetQuickToPortableBridge()); + method->SetIsPortableCompiled(); + } +#else + CHECK(method->GetEntryPointFromQuickCompiledCode() != nullptr); +#endif +} + +void CommonCompilerTest::MakeExecutable(const void* code_start, size_t code_length) { + CHECK(code_start != nullptr); + 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 + // Only warn if not Intel as Intel doesn't have cache flush instructions. +#if !defined(__i386__) && !defined(__x86_64__) + LOG(WARNING) << "UNIMPLEMENTED: cache flush"; +#endif +#endif +} + +void CommonCompilerTest::MakeExecutable(mirror::ClassLoader* class_loader, const char* class_name) { + std::string class_descriptor(DotToDescriptor(class_name)); + Thread* self = Thread::Current(); + StackHandleScope<1> hs(self); + Handle loader(hs.NewHandle(class_loader)); + mirror::Class* klass = class_linker_->FindClass(self, class_descriptor.c_str(), loader); + CHECK(klass != nullptr) << "Class not found " << class_name; + for (size_t i = 0; i < klass->NumDirectMethods(); i++) { + MakeExecutable(klass->GetDirectMethod(i)); + } + for (size_t i = 0; i < klass->NumVirtualMethods(); i++) { + MakeExecutable(klass->GetVirtualMethod(i)); + } +} + +void CommonCompilerTest::SetUp() { + CommonRuntimeTest::SetUp(); + { + ScopedObjectAccess soa(Thread::Current()); + + InstructionSet instruction_set = kRuntimeISA; + + // Take the default set of instruction features from the build. + InstructionSetFeatures instruction_set_features = + ParseFeatureList(Runtime::GetDefaultInstructionSetFeatures()); + +#if defined(__arm__) + InstructionSetFeatures runtime_features = GuessInstructionFeatures(); + + // for ARM, do a runtime check to make sure that the features we are passed from + // the build match the features we actually determine at runtime. + ASSERT_LE(instruction_set_features, runtime_features); +#endif + + runtime_->SetInstructionSet(instruction_set); + for (int i = 0; i < Runtime::kLastCalleeSaveType; i++) { + Runtime::CalleeSaveType type = Runtime::CalleeSaveType(i); + if (!runtime_->HasCalleeSaveMethod(type)) { + runtime_->SetCalleeSaveMethod( + runtime_->CreateCalleeSaveMethod(type), type); + } + } + + // TODO: make selectable + Compiler::Kind compiler_kind + = (kUsePortableCompiler) ? Compiler::kPortable : Compiler::kQuick; + timer_.reset(new CumulativeLogger("Compilation times")); + compiler_driver_.reset(new CompilerDriver(compiler_options_.get(), + verification_results_.get(), + method_inliner_map_.get(), + compiler_kind, instruction_set, + instruction_set_features, + true, new std::set, + 2, true, true, timer_.get())); + } + // We typically don't generate an image in unit tests, disable this optimization by default. + compiler_driver_->SetSupportBootImageFixup(false); +} + +void CommonCompilerTest::SetUpRuntimeOptions(RuntimeOptions* options) { + CommonRuntimeTest::SetUpRuntimeOptions(options); + + compiler_options_.reset(new CompilerOptions); + verification_results_.reset(new VerificationResults(compiler_options_.get())); + method_inliner_map_.reset(new DexFileToMethodInlinerMap); + callbacks_.reset(new QuickCompilerCallbacks(verification_results_.get(), + method_inliner_map_.get())); + options->push_back(std::make_pair("compilercallbacks", callbacks_.get())); +} + +void CommonCompilerTest::TearDown() { + timer_.reset(); + compiler_driver_.reset(); + callbacks_.reset(); + method_inliner_map_.reset(); + verification_results_.reset(); + compiler_options_.reset(); + + CommonRuntimeTest::TearDown(); +} + +void CommonCompilerTest::CompileClass(mirror::ClassLoader* class_loader, const char* class_name) { + std::string class_descriptor(DotToDescriptor(class_name)); + Thread* self = Thread::Current(); + StackHandleScope<1> hs(self); + Handle loader(hs.NewHandle(class_loader)); + mirror::Class* klass = class_linker_->FindClass(self, class_descriptor.c_str(), loader); + CHECK(klass != nullptr) << "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 CommonCompilerTest::CompileMethod(mirror::ArtMethod* method) { + CHECK(method != nullptr); + TimingLogger timings("CommonTest::CompileMethod", false, false); + TimingLogger::ScopedTiming t(__FUNCTION__, &timings); + compiler_driver_->CompileOne(method, &timings); + TimingLogger::ScopedTiming t2("MakeExecutable", &timings); + MakeExecutable(method); +} + +void CommonCompilerTest::CompileDirectMethod(Handle class_loader, + const char* class_name, const char* method_name, + const char* signature) { + std::string class_descriptor(DotToDescriptor(class_name)); + Thread* self = Thread::Current(); + mirror::Class* klass = class_linker_->FindClass(self, class_descriptor.c_str(), class_loader); + CHECK(klass != nullptr) << "Class not found " << class_name; + mirror::ArtMethod* method = klass->FindDirectMethod(method_name, signature); + CHECK(method != nullptr) << "Direct method not found: " + << class_name << "." << method_name << signature; + CompileMethod(method); +} + +void CommonCompilerTest::CompileVirtualMethod(Handle 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)); + Thread* self = Thread::Current(); + mirror::Class* klass = class_linker_->FindClass(self, class_descriptor.c_str(), class_loader); + CHECK(klass != nullptr) << "Class not found " << class_name; + mirror::ArtMethod* method = klass->FindVirtualMethod(method_name, signature); + CHECK(method != NULL) << "Virtual method not found: " + << class_name << "." << method_name << signature; + CompileMethod(method); +} + +void CommonCompilerTest::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. + std::string error_msg; + MemMap::Init(); + image_reservation_.reset(MemMap::MapAnonymous("image reservation", + reinterpret_cast(ART_BASE_ADDRESS), + (size_t)100 * 1024 * 1024, // 100MB + PROT_NONE, + false /* no need for 4gb flag with fixed mmap*/, + &error_msg)); + CHECK(image_reservation_.get() != nullptr) << error_msg; +} + +void CommonCompilerTest::UnreserveImageSpace() { + image_reservation_.reset(); +} + +} // namespace art diff --git a/compiler/common_compiler_test.h b/compiler/common_compiler_test.h new file mode 100644 index 00000000000..df06b71c7dc --- /dev/null +++ b/compiler/common_compiler_test.h @@ -0,0 +1,94 @@ +/* + * 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_COMPILER_COMMON_COMPILER_TEST_H_ +#define ART_COMPILER_COMMON_COMPILER_TEST_H_ + +#include +#include + +#include "common_runtime_test.h" +#include "oat_file.h" + +namespace art { +namespace mirror { + class ClassLoader; +} // namespace mirror + +class CompilerDriver; +class CompilerOptions; +class CumulativeLogger; +class DexFileToMethodInlinerMap; +class VerificationResults; + +template class Handle; + +class CommonCompilerTest : public CommonRuntimeTest { + public: + CommonCompilerTest(); + ~CommonCompilerTest(); + + // Create an OatMethod based on pointers (for unit tests). + OatFile::OatMethod CreateOatMethod(const void* code, const uint8_t* gc_map); + + void MakeExecutable(mirror::ArtMethod* method) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + static void MakeExecutable(const void* code_start, size_t code_length); + + void MakeExecutable(mirror::ClassLoader* class_loader, const char* class_name) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + protected: + virtual void SetUp(); + + virtual void SetUpRuntimeOptions(RuntimeOptions *options); + + virtual void TearDown(); + + void CompileClass(mirror::ClassLoader* class_loader, const char* class_name) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void CompileMethod(mirror::ArtMethod* method) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void CompileDirectMethod(Handle class_loader, const char* class_name, + const char* method_name, const char* signature) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void CompileVirtualMethod(Handle class_loader, const char* class_name, + const char* method_name, const char* signature) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void ReserveImageSpace(); + + void UnreserveImageSpace(); + + std::unique_ptr compiler_options_; + std::unique_ptr verification_results_; + std::unique_ptr method_inliner_map_; + std::unique_ptr callbacks_; + std::unique_ptr compiler_driver_; + std::unique_ptr timer_; + + private: + std::unique_ptr image_reservation_; + + // Chunks must not move their storage after being created - use the node-based std::list. + std::list> header_code_and_maps_chunks_; +}; + +} // namespace art + +#endif // ART_COMPILER_COMMON_COMPILER_TEST_H_ diff --git a/runtime/compiled_class.h b/compiler/compiled_class.h similarity index 88% rename from runtime/compiled_class.h rename to compiler/compiled_class.h index c53d5005022..b88d613ad55 100644 --- a/runtime/compiled_class.h +++ b/compiler/compiled_class.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ART_RUNTIME_COMPILED_CLASS_H_ -#define ART_RUNTIME_COMPILED_CLASS_H_ +#ifndef ART_COMPILER_COMPILED_CLASS_H_ +#define ART_COMPILER_COMPILED_CLASS_H_ #include "mirror/class.h" @@ -34,4 +34,4 @@ class CompiledClass { } // namespace art -#endif // ART_RUNTIME_COMPILED_CLASS_H_ +#endif // ART_COMPILER_COMPILED_CLASS_H_ diff --git a/compiler/compiled_method.cc b/compiler/compiled_method.cc index 29ff390678c..f098a34ea7b 100644 --- a/compiler/compiled_method.cc +++ b/compiler/compiled_method.cc @@ -20,14 +20,16 @@ namespace art { CompiledCode::CompiledCode(CompilerDriver* compiler_driver, InstructionSet instruction_set, - const std::vector& code) - : compiler_driver_(compiler_driver), instruction_set_(instruction_set), code_(nullptr) { - SetCode(code); + const std::vector& quick_code) + : compiler_driver_(compiler_driver), instruction_set_(instruction_set), + portable_code_(nullptr), quick_code_(nullptr) { + SetCode(&quick_code, nullptr); } CompiledCode::CompiledCode(CompilerDriver* compiler_driver, InstructionSet instruction_set, const std::string& elf_object, const std::string& symbol) - : compiler_driver_(compiler_driver), instruction_set_(instruction_set), symbol_(symbol) { + : compiler_driver_(compiler_driver), instruction_set_(instruction_set), + portable_code_(nullptr), quick_code_(nullptr), symbol_(symbol) { CHECK_NE(elf_object.size(), 0U); CHECK_NE(symbol.size(), 0U); std::vector temp_code(elf_object.size()); @@ -38,12 +40,41 @@ CompiledCode::CompiledCode(CompilerDriver* compiler_driver, InstructionSet instr // 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. - SetCode(temp_code); + SetCode(nullptr, &temp_code); } -void CompiledCode::SetCode(const std::vector& code) { - CHECK(!code.empty()); - code_ = compiler_driver_->DeduplicateCode(code); +void CompiledCode::SetCode(const std::vector* quick_code, + const std::vector* portable_code) { + if (portable_code != nullptr) { + CHECK(!portable_code->empty()); + portable_code_ = compiler_driver_->DeduplicateCode(*portable_code); + } + if (quick_code != nullptr) { + CHECK(!quick_code->empty()); + quick_code_ = compiler_driver_->DeduplicateCode(*quick_code); + } +} + +bool CompiledCode::operator==(const CompiledCode& rhs) const { + if (quick_code_ != nullptr) { + if (rhs.quick_code_ == nullptr) { + return false; + } else if (quick_code_->size() != rhs.quick_code_->size()) { + return false; + } else { + return std::equal(quick_code_->begin(), quick_code_->end(), rhs.quick_code_->begin()); + } + } else if (portable_code_ != nullptr) { + if (rhs.portable_code_ == nullptr) { + return false; + } else if (portable_code_->size() != rhs.portable_code_->size()) { + return false; + } else { + return std::equal(portable_code_->begin(), portable_code_->end(), + rhs.portable_code_->begin()); + } + } + return (rhs.quick_code_ == nullptr) && (rhs.portable_code_ == nullptr); } uint32_t CompiledCode::AlignCode(uint32_t offset) const { @@ -51,32 +82,27 @@ uint32_t CompiledCode::AlignCode(uint32_t offset) const { } 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; - } + return RoundUp(offset, GetInstructionSetAlignment(instruction_set)); } size_t CompiledCode::CodeDelta() const { - switch (instruction_set_) { + return CodeDelta(instruction_set_); +} + +size_t CompiledCode::CodeDelta(InstructionSet instruction_set) { + switch (instruction_set) { case kArm: + case kArm64: case kMips: case kX86: + case kX86_64: 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_; + LOG(FATAL) << "Unknown InstructionSet: " << instruction_set; return 0; } } @@ -85,8 +111,10 @@ const void* CompiledCode::CodePointer(const void* code_pointer, InstructionSet instruction_set) { switch (instruction_set) { case kArm: + case kArm64: case kMips: case kX86: + case kX86_64: return code_pointer; case kThumb2: { uintptr_t address = reinterpret_cast(code_pointer); @@ -100,7 +128,6 @@ const void* CompiledCode::CodePointer(const void* code_pointer, } } -#if defined(ART_USE_PORTABLE_COMPILER) const std::string& CompiledCode::GetSymbol() const { CHECK_NE(0U, symbol_.size()); return symbol_; @@ -114,57 +141,59 @@ const std::vector& CompiledCode::GetOatdataOffsetsToCompliledCodeOffse void CompiledCode::AddOatdataOffsetToCompliledCodeOffset(uint32_t offset) { oatdata_offsets_to_compiled_code_offset_.push_back(offset); } -#endif -CompiledMethod::CompiledMethod(CompilerDriver& driver, +CompiledMethod::CompiledMethod(CompilerDriver* driver, InstructionSet instruction_set, - const std::vector& code, + const std::vector& quick_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(&driver, instruction_set, code), frame_size_in_bytes_(frame_size_in_bytes), + const std::vector& native_gc_map, + const std::vector* cfi_info) + : CompiledCode(driver, instruction_set, quick_code), frame_size_in_bytes_(frame_size_in_bytes), core_spill_mask_(core_spill_mask), fp_spill_mask_(fp_spill_mask), - mapping_table_(driver.DeduplicateMappingTable(mapping_table)), - vmap_table_(driver.DeduplicateVMapTable(vmap_table)), - gc_map_(driver.DeduplicateGCMap(native_gc_map)) { + mapping_table_(driver->DeduplicateMappingTable(mapping_table)), + vmap_table_(driver->DeduplicateVMapTable(vmap_table)), + gc_map_(driver->DeduplicateGCMap(native_gc_map)), + cfi_info_(driver->DeduplicateCFIInfo(cfi_info)) { } -CompiledMethod::CompiledMethod(CompilerDriver& driver, +CompiledMethod::CompiledMethod(CompilerDriver* driver, 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(&driver, instruction_set, code), + : CompiledCode(driver, instruction_set, code), frame_size_in_bytes_(frame_size_in_bytes), - core_spill_mask_(core_spill_mask), fp_spill_mask_(fp_spill_mask) { - mapping_table_ = driver.DeduplicateMappingTable(std::vector()); - vmap_table_ = driver.DeduplicateVMapTable(std::vector()); - gc_map_ = driver.DeduplicateGCMap(std::vector()); + core_spill_mask_(core_spill_mask), fp_spill_mask_(fp_spill_mask), + mapping_table_(driver->DeduplicateMappingTable(std::vector())), + vmap_table_(driver->DeduplicateVMapTable(std::vector())), + gc_map_(driver->DeduplicateGCMap(std::vector())), + cfi_info_(nullptr) { } // Constructs a CompiledMethod for the Portable compiler. -CompiledMethod::CompiledMethod(CompilerDriver& driver, InstructionSet instruction_set, +CompiledMethod::CompiledMethod(CompilerDriver* driver, InstructionSet instruction_set, const std::string& code, const std::vector& gc_map, const std::string& symbol) - : CompiledCode(&driver, instruction_set, code, symbol), + : CompiledCode(driver, instruction_set, code, symbol), frame_size_in_bytes_(kStackAlignment), core_spill_mask_(0), - fp_spill_mask_(0), gc_map_(driver.DeduplicateGCMap(gc_map)) { - mapping_table_ = driver.DeduplicateMappingTable(std::vector()); - vmap_table_ = driver.DeduplicateVMapTable(std::vector()); + fp_spill_mask_(0), gc_map_(driver->DeduplicateGCMap(gc_map)) { + mapping_table_ = driver->DeduplicateMappingTable(std::vector()); + vmap_table_ = driver->DeduplicateVMapTable(std::vector()); } -CompiledMethod::CompiledMethod(CompilerDriver& driver, InstructionSet instruction_set, +CompiledMethod::CompiledMethod(CompilerDriver* driver, InstructionSet instruction_set, const std::string& code, const std::string& symbol) - : CompiledCode(&driver, instruction_set, code, symbol), + : CompiledCode(driver, instruction_set, code, symbol), frame_size_in_bytes_(kStackAlignment), core_spill_mask_(0), fp_spill_mask_(0) { - mapping_table_ = driver.DeduplicateMappingTable(std::vector()); - vmap_table_ = driver.DeduplicateVMapTable(std::vector()); - gc_map_ = driver.DeduplicateGCMap(std::vector()); + mapping_table_ = driver->DeduplicateMappingTable(std::vector()); + vmap_table_ = driver->DeduplicateVMapTable(std::vector()); + gc_map_ = driver->DeduplicateGCMap(std::vector()); } } // namespace art diff --git a/compiler/compiled_method.h b/compiler/compiled_method.h index e4fedf1ab47..b8cd851a1f2 100644 --- a/compiler/compiled_method.h +++ b/compiler/compiled_method.h @@ -17,12 +17,12 @@ #ifndef ART_COMPILER_COMPILED_METHOD_H_ #define ART_COMPILER_COMPILED_METHOD_H_ +#include #include #include #include "instruction_set.h" #include "utils.h" -#include "UniquePtr.h" namespace llvm { class Function; @@ -36,7 +36,7 @@ class CompiledCode { public: // For Quick to supply an code blob CompiledCode(CompilerDriver* compiler_driver, InstructionSet instruction_set, - const std::vector& code); + const std::vector& quick_code); // For Portable to supply an ELF object CompiledCode(CompilerDriver* compiler_driver, InstructionSet instruction_set, @@ -46,16 +46,18 @@ class CompiledCode { return instruction_set_; } - const std::vector& GetCode() const { - return *code_; + const std::vector* GetPortableCode() const { + return portable_code_; } - void SetCode(const std::vector& code); - - bool operator==(const CompiledCode& rhs) const { - return (code_ == rhs.code_); + const std::vector* GetQuickCode() const { + return quick_code_; } + void SetCode(const std::vector* quick_code, const std::vector* portable_code); + + bool operator==(const CompiledCode& rhs) const; + // 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. @@ -65,6 +67,7 @@ class CompiledCode { // 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; + static size_t CodeDelta(InstructionSet instruction_set); // Returns a pointer suitable for invoking the code at the argument // code_pointer address. Mainly to cope with kThumb2 where the @@ -72,19 +75,20 @@ class CompiledCode { 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: - CompilerDriver* compiler_driver_; + CompilerDriver* const compiler_driver_; const InstructionSet instruction_set_; - // Used to store the PIC code for Quick and an ELF image for portable. - std::vector* code_; + // The ELF image for portable. + std::vector* portable_code_; + + // Used to store the PIC code for Quick. + std::vector* quick_code_; // Used for the Portable ELF symbol name. const std::string symbol_; @@ -99,30 +103,31 @@ class CompiledCode { class CompiledMethod : public CompiledCode { public: // Constructs a CompiledMethod for the non-LLVM compilers. - CompiledMethod(CompilerDriver& driver, + CompiledMethod(CompilerDriver* driver, InstructionSet instruction_set, - const std::vector& code, + const std::vector& quick_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); + const std::vector& native_gc_map, + const std::vector* cfi_info); - // Constructs a CompiledMethod for the JniCompiler. - CompiledMethod(CompilerDriver& driver, + // Constructs a CompiledMethod for the QuickJniCompiler. + CompiledMethod(CompilerDriver* driver, InstructionSet instruction_set, - const std::vector& code, + const std::vector& quick_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(CompilerDriver& driver, InstructionSet instruction_set, const std::string& code, + CompiledMethod(CompilerDriver* driver, InstructionSet instruction_set, const std::string& code, const std::vector& gc_map, const std::string& symbol); // Constructs a CompiledMethod for the Portable JniCompiler. - CompiledMethod(CompilerDriver& driver, InstructionSet instruction_set, const std::string& code, + CompiledMethod(CompilerDriver* driver, InstructionSet instruction_set, const std::string& code, const std::string& symbol); ~CompiledMethod() {} @@ -154,6 +159,10 @@ class CompiledMethod : public CompiledCode { return *gc_map_; } + const std::vector* GetCFIInfo() const { + return cfi_info_; + } + private: // For quick code, the size of the activation used by the code. const size_t frame_size_in_bytes_; @@ -169,6 +178,8 @@ class CompiledMethod : public CompiledCode { // 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_; + // For quick code, a FDE entry for the debug_frame section. + std::vector* cfi_info_; }; } // namespace art diff --git a/compiler/compiler.cc b/compiler/compiler.cc new file mode 100644 index 00000000000..a832c310eb2 --- /dev/null +++ b/compiler/compiler.cc @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compiler.h" +#include "compilers.h" +#include "driver/compiler_driver.h" +#include "mirror/art_method-inl.h" + +#ifdef ART_USE_PORTABLE_COMPILER +#include "dex/portable/mir_to_gbc.h" +#include "elf_writer_mclinker.h" +#endif + +namespace art { + +#ifdef ART_SEA_IR_MODE +extern "C" art::CompiledMethod* SeaIrCompileMethod(const art::DexFile::CodeItem* code_item, + uint32_t access_flags, + art::InvokeType invoke_type, + uint16_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const art::DexFile& dex_file); +#endif + + +CompiledMethod* Compiler::TryCompileWithSeaIR(const art::DexFile::CodeItem* code_item, + uint32_t access_flags, + art::InvokeType invoke_type, + uint16_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const art::DexFile& dex_file) { +#ifdef ART_SEA_IR_MODE + bool use_sea = (std::string::npos != PrettyMethod(method_idx, dex_file).find("fibonacci")); + if (use_sea) { + LOG(INFO) << "Using SEA IR to compile..." << std::endl; + return SeaIrCompileMethod(code_item, + access_flags, + invoke_type, + class_def_idx, + method_idx, + class_loader, + dex_file); + } +#endif + return nullptr; +} + + +#ifdef ART_USE_PORTABLE_COMPILER + +extern "C" void ArtInitCompilerContext(art::CompilerDriver* driver); + +extern "C" void ArtUnInitCompilerContext(art::CompilerDriver* driver); + +extern "C" art::CompiledMethod* ArtCompileMethod(art::CompilerDriver* driver, + const art::DexFile::CodeItem* code_item, + uint32_t access_flags, + art::InvokeType invoke_type, + uint16_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const art::DexFile& dex_file); + +extern "C" art::CompiledMethod* ArtLLVMJniCompileMethod(art::CompilerDriver* driver, + uint32_t access_flags, uint32_t method_idx, + const art::DexFile& dex_file); + +extern "C" void compilerLLVMSetBitcodeFileName(art::CompilerDriver* driver, + std::string const& filename); + + +class LLVMCompiler FINAL : public Compiler { + public: + explicit LLVMCompiler(CompilerDriver* driver) : Compiler(driver, 1000) {} + + void Init() const OVERRIDE { + ArtInitCompilerContext(GetCompilerDriver()); + } + + void UnInit() const OVERRIDE { + ArtUnInitCompilerContext(GetCompilerDriver()); + } + + CompiledMethod* Compile(const DexFile::CodeItem* code_item, + uint32_t access_flags, + InvokeType invoke_type, + uint16_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const DexFile& dex_file) const OVERRIDE { + CompiledMethod* method = TryCompileWithSeaIR(code_item, + access_flags, + invoke_type, + class_def_idx, + method_idx, + class_loader, + dex_file); + if (method != nullptr) { + return method; + } + + return ArtCompileMethod(GetCompilerDriver(), + code_item, + access_flags, + invoke_type, + class_def_idx, + method_idx, + class_loader, + dex_file); + } + + CompiledMethod* JniCompile(uint32_t access_flags, + uint32_t method_idx, + const DexFile& dex_file) const OVERRIDE { + return ArtLLVMJniCompileMethod(GetCompilerDriver(), access_flags, method_idx, dex_file); + } + + uintptr_t GetEntryPointOf(mirror::ArtMethod* method) const { + return reinterpret_cast(method->GetEntryPointFromPortableCompiledCode()); + } + + bool WriteElf(art::File* file, + OatWriter* oat_writer, + const std::vector& dex_files, + const std::string& android_root, + bool is_host, const CompilerDriver& driver) const + OVERRIDE + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + return art::ElfWriterMclinker::Create( + file, oat_writer, dex_files, android_root, is_host, driver); + } + + Backend* GetCodeGenerator(CompilationUnit* cu, void* compilation_unit) const { + return PortableCodeGenerator( + cu, cu->mir_graph.get(), &cu->arena, + reinterpret_cast(compilation_unit)); + } + + void InitCompilationUnit(CompilationUnit& cu) const { + // Fused long branches not currently useful in bitcode. + cu.disable_opt |= + (1 << kBranchFusing) | + (1 << kSuppressExceptionEdges); + } + + bool IsPortable() const OVERRIDE { + return true; + } + + void SetBitcodeFileName(const CompilerDriver& driver, const std::string& filename) { + typedef void (*SetBitcodeFileNameFn)(const CompilerDriver&, const std::string&); + + SetBitcodeFileNameFn set_bitcode_file_name = + reinterpret_cast(compilerLLVMSetBitcodeFileName); + + set_bitcode_file_name(driver, filename); + } + + private: + DISALLOW_COPY_AND_ASSIGN(LLVMCompiler); +}; +#endif + +Compiler* Compiler::Create(CompilerDriver* driver, Compiler::Kind kind) { + switch (kind) { + case kQuick: + return new QuickCompiler(driver); + break; + case kOptimizing: + return new OptimizingCompiler(driver); + break; + case kPortable: +#ifdef ART_USE_PORTABLE_COMPILER + return new LLVMCompiler(driver); +#else + LOG(FATAL) << "Portable compiler not compiled"; +#endif + break; + default: + LOG(FATAL) << "UNREACHABLE"; + } + return nullptr; +} + +} // namespace art diff --git a/compiler/compiler.h b/compiler/compiler.h new file mode 100644 index 00000000000..4caebf32cd0 --- /dev/null +++ b/compiler/compiler.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_COMPILER_H_ +#define ART_COMPILER_COMPILER_H_ + +#include "dex_file.h" +#include "os.h" + +namespace art { + +class Backend; +struct CompilationUnit; +class CompilerDriver; +class CompiledMethod; +class MIRGraph; +class OatWriter; + +namespace mirror { + class ArtMethod; +} + +class Compiler { + public: + enum Kind { + kQuick, + kOptimizing, + kPortable + }; + + static Compiler* Create(CompilerDriver* driver, Kind kind); + + virtual void Init() const = 0; + + virtual void UnInit() const = 0; + + virtual CompiledMethod* Compile(const DexFile::CodeItem* code_item, + uint32_t access_flags, + InvokeType invoke_type, + uint16_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const DexFile& dex_file) const = 0; + + static CompiledMethod* TryCompileWithSeaIR(const art::DexFile::CodeItem* code_item, + uint32_t access_flags, + art::InvokeType invoke_type, + uint16_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const art::DexFile& dex_file); + + virtual CompiledMethod* JniCompile(uint32_t access_flags, + uint32_t method_idx, + const DexFile& dex_file) const = 0; + + virtual uintptr_t GetEntryPointOf(mirror::ArtMethod* method) const + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) = 0; + + virtual bool WriteElf(art::File* file, + OatWriter* oat_writer, + const std::vector& dex_files, + const std::string& android_root, + bool is_host) const + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) = 0; + + virtual Backend* GetCodeGenerator(CompilationUnit* cu, void* compilation_unit) const = 0; + + uint64_t GetMaximumCompilationTimeBeforeWarning() const { + return maximum_compilation_time_before_warning_; + } + + virtual bool IsPortable() const { + return false; + } + + void SetBitcodeFileName(const CompilerDriver& driver, const std::string& filename) { + UNUSED(driver); + UNUSED(filename); + } + + virtual void InitCompilationUnit(CompilationUnit& cu) const = 0; + + virtual ~Compiler() {} + + /* + * @brief Generate and return Dwarf CFI initialization, if supported by the + * backend. + * @param driver CompilerDriver for this compile. + * @returns nullptr if not supported by backend or a vector of bytes for CFI DWARF + * information. + * @note This is used for backtrace information in generated code. + */ + virtual std::vector* GetCallFrameInformationInitialization(const CompilerDriver& driver) + const { + return nullptr; + } + + protected: + explicit Compiler(CompilerDriver* driver, uint64_t warning) : + driver_(driver), maximum_compilation_time_before_warning_(warning) { + } + + CompilerDriver* GetCompilerDriver() const { + return driver_; + } + + private: + CompilerDriver* const driver_; + const uint64_t maximum_compilation_time_before_warning_; + + DISALLOW_COPY_AND_ASSIGN(Compiler); +}; + +} // namespace art + +#endif // ART_COMPILER_COMPILER_H_ diff --git a/compiler/compilers.cc b/compiler/compilers.cc new file mode 100644 index 00000000000..250924ad304 --- /dev/null +++ b/compiler/compilers.cc @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compilers.h" + +#include "dex/mir_graph.h" +#include "dex/quick/mir_to_lir.h" +#include "elf_writer_quick.h" +#include "mirror/art_method-inl.h" + +namespace art { + +extern "C" void ArtInitQuickCompilerContext(art::CompilerDriver* driver); +extern "C" void ArtUnInitQuickCompilerContext(art::CompilerDriver* driver); +extern "C" art::CompiledMethod* ArtQuickCompileMethod(art::CompilerDriver* driver, + const art::DexFile::CodeItem* code_item, + uint32_t access_flags, + art::InvokeType invoke_type, + uint16_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const art::DexFile& dex_file); + +extern "C" art::CompiledMethod* ArtQuickJniCompileMethod(art::CompilerDriver* driver, + uint32_t access_flags, uint32_t method_idx, + const art::DexFile& dex_file); + +// Hack for CFI CIE initialization +extern std::vector* X86CFIInitialization(bool is_x86_64); + +void QuickCompiler::Init() const { + ArtInitQuickCompilerContext(GetCompilerDriver()); +} + +void QuickCompiler::UnInit() const { + ArtUnInitQuickCompilerContext(GetCompilerDriver()); +} + +CompiledMethod* QuickCompiler::Compile(const DexFile::CodeItem* code_item, + uint32_t access_flags, + InvokeType invoke_type, + uint16_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const DexFile& dex_file) const { + CompiledMethod* method = TryCompileWithSeaIR(code_item, + access_flags, + invoke_type, + class_def_idx, + method_idx, + class_loader, + dex_file); + if (method != nullptr) { + return method; + } + + return ArtQuickCompileMethod(GetCompilerDriver(), + code_item, + access_flags, + invoke_type, + class_def_idx, + method_idx, + class_loader, + dex_file); +} + +CompiledMethod* QuickCompiler::JniCompile(uint32_t access_flags, + uint32_t method_idx, + const DexFile& dex_file) const { + return ArtQuickJniCompileMethod(GetCompilerDriver(), access_flags, method_idx, dex_file); +} + +uintptr_t QuickCompiler::GetEntryPointOf(mirror::ArtMethod* method) const { + return reinterpret_cast(method->GetEntryPointFromQuickCompiledCode()); +} + +bool QuickCompiler::WriteElf(art::File* file, + OatWriter* oat_writer, + const std::vector& dex_files, + const std::string& android_root, + bool is_host) const { + return art::ElfWriterQuick::Create(file, oat_writer, dex_files, android_root, is_host, + *GetCompilerDriver()); +} + +Backend* QuickCompiler::GetCodeGenerator(CompilationUnit* cu, void* compilation_unit) const { + Mir2Lir* mir_to_lir = nullptr; + switch (cu->instruction_set) { + case kThumb2: + mir_to_lir = ArmCodeGenerator(cu, cu->mir_graph.get(), &cu->arena); + break; + case kArm64: + mir_to_lir = Arm64CodeGenerator(cu, cu->mir_graph.get(), &cu->arena); + break; + case kMips: + mir_to_lir = MipsCodeGenerator(cu, cu->mir_graph.get(), &cu->arena); + break; + case kX86: + // Fall-through. + case kX86_64: + mir_to_lir = X86CodeGenerator(cu, cu->mir_graph.get(), &cu->arena); + break; + default: + LOG(FATAL) << "Unexpected instruction set: " << cu->instruction_set; + } + + /* The number of compiler temporaries depends on backend so set it up now if possible */ + if (mir_to_lir) { + size_t max_temps = mir_to_lir->GetMaxPossibleCompilerTemps(); + bool set_max = cu->mir_graph->SetMaxAvailableNonSpecialCompilerTemps(max_temps); + CHECK(set_max); + } + return mir_to_lir; +} + +std::vector* QuickCompiler::GetCallFrameInformationInitialization( + const CompilerDriver& driver) const { + if (driver.GetInstructionSet() == kX86) { + return X86CFIInitialization(false); + } + if (driver.GetInstructionSet() == kX86_64) { + return X86CFIInitialization(true); + } + return nullptr; +} + +CompiledMethod* OptimizingCompiler::Compile(const DexFile::CodeItem* code_item, + uint32_t access_flags, + InvokeType invoke_type, + uint16_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const DexFile& dex_file) const { + CompiledMethod* method = TryCompile(code_item, access_flags, invoke_type, class_def_idx, + method_idx, class_loader, dex_file); + if (method != nullptr) { + return method; + } + + return QuickCompiler::Compile(code_item, access_flags, invoke_type, class_def_idx, method_idx, + class_loader, dex_file); +} + +} // namespace art diff --git a/compiler/compilers.h b/compiler/compilers.h new file mode 100644 index 00000000000..2c231e1212f --- /dev/null +++ b/compiler/compilers.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_COMPILERS_H_ +#define ART_COMPILER_COMPILERS_H_ + +#include "compiler.h" + +namespace art { + +class QuickCompiler : public Compiler { + public: + explicit QuickCompiler(CompilerDriver* driver) : Compiler(driver, 100) {} + + void Init() const OVERRIDE; + + void UnInit() const OVERRIDE; + + CompiledMethod* Compile(const DexFile::CodeItem* code_item, + uint32_t access_flags, + InvokeType invoke_type, + uint16_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const DexFile& dex_file) const OVERRIDE; + + CompiledMethod* JniCompile(uint32_t access_flags, + uint32_t method_idx, + const DexFile& dex_file) const OVERRIDE; + + uintptr_t GetEntryPointOf(mirror::ArtMethod* method) const OVERRIDE + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + bool WriteElf(art::File* file, + OatWriter* oat_writer, + const std::vector& dex_files, + const std::string& android_root, + bool is_host) const + OVERRIDE + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + Backend* GetCodeGenerator(CompilationUnit* cu, void* compilation_unit) const OVERRIDE; + + void InitCompilationUnit(CompilationUnit& cu) const OVERRIDE {} + + /* + * @brief Generate and return Dwarf CFI initialization, if supported by the + * backend. + * @param driver CompilerDriver for this compile. + * @returns nullptr if not supported by backend or a vector of bytes for CFI DWARF + * information. + * @note This is used for backtrace information in generated code. + */ + std::vector* GetCallFrameInformationInitialization(const CompilerDriver& driver) const + OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(QuickCompiler); +}; + +class OptimizingCompiler FINAL : public QuickCompiler { + public: + explicit OptimizingCompiler(CompilerDriver* driver); + + CompiledMethod* Compile(const DexFile::CodeItem* code_item, + uint32_t access_flags, + InvokeType invoke_type, + uint16_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const DexFile& dex_file) const OVERRIDE; + + CompiledMethod* TryCompile(const DexFile::CodeItem* code_item, + uint32_t access_flags, + InvokeType invoke_type, + uint16_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const DexFile& dex_file) const; + + private: + std::unique_ptr visualizer_output_; + + DISALLOW_COPY_AND_ASSIGN(OptimizingCompiler); +}; + +} // namespace art + +#endif // ART_COMPILER_COMPILERS_H_ diff --git a/compiler/dex/arena_allocator.cc b/compiler/dex/arena_allocator.cc deleted file mode 100644 index 36393e73877..00000000000 --- a/compiler/dex/arena_allocator.cc +++ /dev/null @@ -1,189 +0,0 @@ -/* - * 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" -#include "base/mutex.h" - -namespace art { - -// Memmap is a bit slower than malloc according to my measurements. -static constexpr bool kUseMemMap = false; -static constexpr bool kUseMemSet = true && kUseMemMap; - -static const char* alloc_names[ArenaAllocator::kNumAllocKinds] = { - "Misc ", - "BasicBlock ", - "LIR ", - "MIR ", - "DataFlow ", - "GrowList ", - "GrowBitMap ", - "Dalvik2SSA ", - "DebugInfo ", - "Successor ", - "RegAlloc ", - "Data ", - "Preds ", -}; - -Arena::Arena(size_t size) - : bytes_allocated_(0), - map_(nullptr), - next_(nullptr) { - if (kUseMemMap) { - map_ = MemMap::MapAnonymous("dalvik-arena", NULL, size, PROT_READ | PROT_WRITE); - memory_ = map_->Begin(); - size_ = map_->Size(); - } else { - memory_ = reinterpret_cast(calloc(1, size)); - size_ = size; - } -} - -Arena::~Arena() { - if (kUseMemMap) { - delete map_; - } else { - free(reinterpret_cast(memory_)); - } -} - -void Arena::Reset() { - if (bytes_allocated_) { - if (kUseMemSet || !kUseMemMap) { - memset(Begin(), 0, bytes_allocated_); - } else { - madvise(Begin(), bytes_allocated_, MADV_DONTNEED); - } - bytes_allocated_ = 0; - } -} - -ArenaPool::ArenaPool() - : lock_("Arena pool lock"), - free_arenas_(nullptr) { -} - -ArenaPool::~ArenaPool() { - while (free_arenas_ != nullptr) { - auto* arena = free_arenas_; - free_arenas_ = free_arenas_->next_; - delete arena; - } -} - -Arena* ArenaPool::AllocArena(size_t size) { - Thread* self = Thread::Current(); - Arena* ret = nullptr; - { - MutexLock lock(self, lock_); - if (free_arenas_ != nullptr && LIKELY(free_arenas_->Size() >= size)) { - ret = free_arenas_; - free_arenas_ = free_arenas_->next_; - } - } - if (ret == nullptr) { - ret = new Arena(size); - } - ret->Reset(); - return ret; -} - -void ArenaPool::FreeArena(Arena* arena) { - Thread* self = Thread::Current(); - { - MutexLock lock(self, lock_); - arena->next_ = free_arenas_; - free_arenas_ = arena; - } -} - -size_t ArenaAllocator::BytesAllocated() const { - size_t total = 0; - for (int i = 0; i < kNumAllocKinds; i++) { - total += alloc_stats_[i]; - } - return total; -} - -ArenaAllocator::ArenaAllocator(ArenaPool* pool) - : pool_(pool), - begin_(nullptr), - end_(nullptr), - ptr_(nullptr), - arena_head_(nullptr), - num_allocations_(0) { - memset(&alloc_stats_[0], 0, sizeof(alloc_stats_)); -} - -void ArenaAllocator::UpdateBytesAllocated() { - if (arena_head_ != nullptr) { - // Update how many bytes we have allocated into the arena so that the arena pool knows how - // much memory to zero out. - arena_head_->bytes_allocated_ = ptr_ - begin_; - } -} - -ArenaAllocator::~ArenaAllocator() { - // Reclaim all the arenas by giving them back to the thread pool. - UpdateBytesAllocated(); - while (arena_head_ != nullptr) { - Arena* arena = arena_head_; - arena_head_ = arena_head_->next_; - pool_->FreeArena(arena); - } -} - -void ArenaAllocator::ObtainNewArenaForAllocation(size_t allocation_size) { - UpdateBytesAllocated(); - Arena* new_arena = pool_->AllocArena(std::max(Arena::kDefaultSize, allocation_size)); - new_arena->next_ = arena_head_; - arena_head_ = new_arena; - // Update our internal data structures. - ptr_ = begin_ = new_arena->Begin(); - end_ = new_arena->End(); -} - -// Dump memory usage stats. -void ArenaAllocator::DumpMemStats(std::ostream& os) const { - size_t malloc_bytes = 0; - // Start out with how many lost bytes we have in the arena we are currently allocating into. - size_t lost_bytes(end_ - ptr_); - size_t num_arenas = 0; - for (Arena* arena = arena_head_; arena != nullptr; arena = arena->next_) { - malloc_bytes += arena->Size(); - if (arena != arena_head_) { - lost_bytes += arena->RemainingSpace(); - } - ++num_arenas; - } - const size_t bytes_allocated = BytesAllocated(); - os << " MEM: used: " << bytes_allocated << ", allocated: " << malloc_bytes - << ", lost: " << lost_bytes << "\n"; - if (num_allocations_ != 0) { - os << "Number of arenas allocated: " << num_arenas << ", Number of allocations: " - << num_allocations_ << ", avg size: " << bytes_allocated / 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/compiler/dex/arena_allocator.h b/compiler/dex/arena_allocator.h deleted file mode 100644 index dda52a2ed0a..00000000000 --- a/compiler/dex/arena_allocator.h +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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_COMPILER_DEX_ARENA_ALLOCATOR_H_ -#define ART_COMPILER_DEX_ARENA_ALLOCATOR_H_ - -#include -#include - -#include "base/mutex.h" -#include "compiler_enums.h" -#include "mem_map.h" - -namespace art { - -class Arena; -class ArenaPool; -class ArenaAllocator; - -class Arena { - public: - static constexpr size_t kDefaultSize = 128 * KB; - explicit Arena(size_t size = kDefaultSize); - ~Arena(); - void Reset(); - uint8_t* Begin() { - return memory_; - } - - uint8_t* End() { - return memory_ + size_; - } - - size_t Size() const { - return size_; - } - - size_t RemainingSpace() const { - return Size() - bytes_allocated_; - } - - private: - size_t bytes_allocated_; - uint8_t* memory_; - size_t size_; - MemMap* map_; - Arena* next_; - friend class ArenaPool; - friend class ArenaAllocator; - DISALLOW_COPY_AND_ASSIGN(Arena); -}; - -class ArenaPool { - public: - ArenaPool(); - ~ArenaPool(); - Arena* AllocArena(size_t size); - void FreeArena(Arena* arena); - - private: - Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; - Arena* free_arenas_ GUARDED_BY(lock_); - DISALLOW_COPY_AND_ASSIGN(ArenaPool); -}; - -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 - }; - - static constexpr bool kCountAllocations = false; - - explicit ArenaAllocator(ArenaPool* pool); - ~ArenaAllocator(); - - // Returns zeroed memory. - void* Alloc(size_t bytes, ArenaAllocKind kind) ALWAYS_INLINE { - bytes = (bytes + 3) & ~3; - if (UNLIKELY(ptr_ + bytes > end_)) { - // Obtain a new block. - ObtainNewArenaForAllocation(bytes); - if (UNLIKELY(ptr_ == nullptr)) { - return nullptr; - } - } - if (kCountAllocations) { - alloc_stats_[kind] += bytes; - ++num_allocations_; - } - uint8_t* ret = ptr_; - ptr_ += bytes; - return ret; - } - - void ObtainNewArenaForAllocation(size_t allocation_size); - size_t BytesAllocated() const; - void DumpMemStats(std::ostream& os) const; - - private: - void UpdateBytesAllocated(); - - ArenaPool* pool_; - uint8_t* begin_; - uint8_t* end_; - uint8_t* ptr_; - Arena* arena_head_; - - // Statistics. - size_t num_allocations_; - size_t alloc_stats_[kNumAllocKinds]; // Bytes used by various allocation kinds. - - DISALLOW_COPY_AND_ASSIGN(ArenaAllocator); -}; // ArenaAllocator - -struct MemStats { - public: - void Dump(std::ostream& os) const { - arena_.DumpMemStats(os); - } - explicit MemStats(const ArenaAllocator &arena) : arena_(arena) {} - private: - const ArenaAllocator &arena_; -}; // MemStats - -} // namespace art - -#endif // ART_COMPILER_DEX_ARENA_ALLOCATOR_H_ diff --git a/compiler/dex/arena_bit_vector.cc b/compiler/dex/arena_bit_vector.cc deleted file mode 100644 index 3fa92952767..00000000000 --- a/compiler/dex/arena_bit_vector.cc +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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_->Alloc(storage_size_ * sizeof(uint32_t), - 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_->Alloc(new_size * sizeof(uint32_t), - 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); - } -} - -// 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; -} - -/* - * 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/compiler/dex/arena_bit_vector.h b/compiler/dex/arena_bit_vector.h deleted file mode 100644 index 8bcd628dc0a..00000000000 --- a/compiler/dex/arena_bit_vector.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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_COMPILER_DEX_ARENA_BIT_VECTOR_H_ -#define ART_COMPILER_DEX_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: - explicit 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) {} - - // Return the position of the next set bit. -1 means end-of-element reached. - int 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_ / 32; - uint32_t word = bit_storage_[word_index]; - // Mask out any bits in the first word we've already considered. - word >>= bit_index_ & 0x1f; - if (word == 0) { - bit_index_ &= ~0x1f; - do { - word_index++; - if ((word_index * 32) >= bit_size_) { - bit_index_ = bit_size_; - return -1; - } - word = bit_storage_[word_index]; - bit_index_ += 32; - } while (word == 0); - } - bit_index_ += CTZ(word) + 1; - return bit_index_ - 1; - } - - static void* operator new(size_t size, ArenaAllocator* arena) { - return arena->Alloc(sizeof(ArenaBitVector::Iterator), - 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->Alloc(sizeof(ArenaBitVector), 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); - // Are we equal to another bit vector? Note: expandability attributes must also match. - bool Equal(const ArenaBitVector* src) { - return (storage_size_ == src->GetStorageSize()) && - (expandable_ == src->IsExpandable()) && - (memcmp(storage_, src->GetRawStorage(), storage_size_ * 4) == 0); - } - 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_; } - const uint32_t* GetRawStorage() const { 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_COMPILER_DEX_ARENA_BIT_VECTOR_H_ diff --git a/compiler/dex/backend.h b/compiler/dex/backend.h index 01959b77f58..1f24849257e 100644 --- a/compiler/dex/backend.h +++ b/compiler/dex/backend.h @@ -17,17 +17,36 @@ #ifndef ART_COMPILER_DEX_BACKEND_H_ #define ART_COMPILER_DEX_BACKEND_H_ -#include "compiled_method.h" -#include "arena_allocator.h" - namespace art { +class ArenaAllocator; +class CompiledMethod; + class Backend { public: virtual ~Backend() {} virtual void Materialize() = 0; virtual CompiledMethod* GetCompiledMethod() = 0; + // Queries for backend support for vectors + /* + * Return the number of bits in a vector register. + * @return 0 if vector registers are not supported, or the + * number of bits in the vector register if supported. + */ + virtual int VectorRegisterSize() { return 0; } + + /* + * Return the number of reservable vector registers supported + * @param fp_used ‘true’ if floating point computations will be + * executed while vector registers are reserved. + * @return the number of vector registers that are available + * @note The backend should ensure that sufficient vector registers + * are held back to generate scalar code without exhausting vector + * registers, if scalar code also uses the vector registers. + */ + virtual int NumReservableVectorRegisters(bool fp_used) { return 0; } + protected: explicit Backend(ArenaAllocator* arena) : arena_(arena) {} ArenaAllocator* const arena_; diff --git a/compiler/dex/bb_optimizations.cc b/compiler/dex/bb_optimizations.cc new file mode 100644 index 00000000000..920cde28aa6 --- /dev/null +++ b/compiler/dex/bb_optimizations.cc @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bb_optimizations.h" +#include "dataflow_iterator.h" +#include "dataflow_iterator-inl.h" + +namespace art { + +/* + * Code Layout pass implementation start. + */ +bool CodeLayout::Worker(const PassDataHolder* data) const { + DCHECK(data != nullptr); + const PassMEDataHolder* pass_me_data_holder = down_cast(data); + CompilationUnit* c_unit = pass_me_data_holder->c_unit; + DCHECK(c_unit != nullptr); + BasicBlock* bb = pass_me_data_holder->bb; + DCHECK(bb != nullptr); + c_unit->mir_graph->LayoutBlocks(bb); + // No need of repeating, so just return false. + return false; +} + +/* + * BasicBlock Combine pass implementation start. + */ +bool BBCombine::Worker(const PassDataHolder* data) const { + DCHECK(data != nullptr); + const PassMEDataHolder* pass_me_data_holder = down_cast(data); + CompilationUnit* c_unit = pass_me_data_holder->c_unit; + DCHECK(c_unit != nullptr); + BasicBlock* bb = pass_me_data_holder->bb; + DCHECK(bb != nullptr); + c_unit->mir_graph->CombineBlocks(bb); + + // No need of repeating, so just return false. + return false; +} + +/* + * BasicBlock Optimization pass implementation start. + */ +void BBOptimizations::Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + /* + * This pass has a different ordering depEnding on the suppress exception, + * so do the pass here for now: + * - Later, the Start should just change the ordering and we can move the extended + * creation into the pass driver's main job with a new iterator + */ + c_unit->mir_graph->BasicBlockOptimization(); +} + +} // namespace art diff --git a/compiler/dex/bb_optimizations.h b/compiler/dex/bb_optimizations.h new file mode 100644 index 00000000000..8c3dbd646db --- /dev/null +++ b/compiler/dex/bb_optimizations.h @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_DEX_BB_OPTIMIZATIONS_H_ +#define ART_COMPILER_DEX_BB_OPTIMIZATIONS_H_ + +#include "compiler_internals.h" +#include "pass_me.h" + +namespace art { + +/** + * @class CacheFieldLoweringInfo + * @brief Cache the lowering info for fields used by IGET/IPUT/SGET/SPUT insns. + */ +class CacheFieldLoweringInfo : public PassME { + public: + CacheFieldLoweringInfo() : PassME("CacheFieldLoweringInfo", kNoNodes) { + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + cUnit->mir_graph->DoCacheFieldLoweringInfo(); + } + + bool Gate(const PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + return cUnit->mir_graph->HasFieldAccess(); + } +}; + +/** + * @class CacheMethodLoweringInfo + * @brief Cache the lowering info for methods called by INVOKEs. + */ +class CacheMethodLoweringInfo : public PassME { + public: + CacheMethodLoweringInfo() : PassME("CacheMethodLoweringInfo", kNoNodes) { + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + cUnit->mir_graph->DoCacheMethodLoweringInfo(); + } + + bool Gate(const PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + return cUnit->mir_graph->HasInvokes(); + } +}; + +/** + * @class SpecialMethodInliner + * @brief Performs method inlining pass on special kinds of methods. + * @details Special methods are methods that fall in one of the following categories: + * empty, instance getter, instance setter, argument return, and constant return. + */ +class SpecialMethodInliner : public PassME { + public: + SpecialMethodInliner() : PassME("SpecialMethodInliner") { + } + + bool Gate(const PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + return cUnit->mir_graph->InlineSpecialMethodsGate(); + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + cUnit->mir_graph->InlineSpecialMethodsStart(); + } + + bool Worker(const PassDataHolder* data) const { + DCHECK(data != nullptr); + const PassMEDataHolder* pass_me_data_holder = down_cast(data); + CompilationUnit* cUnit = pass_me_data_holder->c_unit; + DCHECK(cUnit != nullptr); + BasicBlock* bb = pass_me_data_holder->bb; + DCHECK(bb != nullptr); + cUnit->mir_graph->InlineSpecialMethods(bb); + // No need of repeating, so just return false. + return false; + } + + void End(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + cUnit->mir_graph->InlineSpecialMethodsEnd(); + } +}; + +/** + * @class CodeLayout + * @brief Perform the code layout pass. + */ +class CodeLayout : public PassME { + public: + CodeLayout() : PassME("CodeLayout", kAllNodes, kOptimizationBasicBlockChange, "2_post_layout_cfg") { + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + cUnit->mir_graph->VerifyDataflow(); + } + + bool Worker(const PassDataHolder* data) const; +}; + +/** + * @class NullCheckEliminationAndTypeInference + * @brief Null check elimination and type inference. + */ +class NullCheckEliminationAndTypeInference : public PassME { + public: + NullCheckEliminationAndTypeInference() + : PassME("NCE_TypeInference", kRepeatingTopologicalSortTraversal, "4_post_nce_cfg") { + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + cUnit->mir_graph->EliminateNullChecksAndInferTypesStart(); + } + + bool Worker(const PassDataHolder* data) const { + DCHECK(data != nullptr); + const PassMEDataHolder* pass_me_data_holder = down_cast(data); + CompilationUnit* cUnit = pass_me_data_holder->c_unit; + DCHECK(cUnit != nullptr); + BasicBlock* bb = pass_me_data_holder->bb; + DCHECK(bb != nullptr); + return cUnit->mir_graph->EliminateNullChecksAndInferTypes(bb); + } + + void End(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + cUnit->mir_graph->EliminateNullChecksAndInferTypesEnd(); + } +}; + +class ClassInitCheckElimination : public PassME { + public: + ClassInitCheckElimination() + : PassME("ClInitCheckElimination", kLoopRepeatingTopologicalSortTraversal) { + } + + bool Gate(const PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + return cUnit->mir_graph->EliminateClassInitChecksGate(); + } + + bool Worker(const PassDataHolder* data) const { + DCHECK(data != nullptr); + const PassMEDataHolder* pass_me_data_holder = down_cast(data); + CompilationUnit* cUnit = pass_me_data_holder->c_unit; + DCHECK(cUnit != nullptr); + BasicBlock* bb = pass_me_data_holder->bb; + DCHECK(bb != nullptr); + return cUnit->mir_graph->EliminateClassInitChecks(bb); + } + + void End(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + cUnit->mir_graph->EliminateClassInitChecksEnd(); + } +}; + +/** + * @class GlobalValueNumberingPass + * @brief Performs the global value numbering pass. + */ +class GlobalValueNumberingPass : public PassME { + public: + GlobalValueNumberingPass() + : PassME("GVN", kLoopRepeatingTopologicalSortTraversal, "4_post_gvn_cfg") { + } + + bool Gate(const PassDataHolder* data) const OVERRIDE { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + return cUnit->mir_graph->ApplyGlobalValueNumberingGate(); + } + + bool Worker(const PassDataHolder* data) const OVERRIDE { + DCHECK(data != nullptr); + const PassMEDataHolder* pass_me_data_holder = down_cast(data); + CompilationUnit* cUnit = pass_me_data_holder->c_unit; + DCHECK(cUnit != nullptr); + BasicBlock* bb = pass_me_data_holder->bb; + DCHECK(bb != nullptr); + return cUnit->mir_graph->ApplyGlobalValueNumbering(bb); + } + + void End(PassDataHolder* data) const OVERRIDE { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + cUnit->mir_graph->ApplyGlobalValueNumberingEnd(); + } +}; + +/** + * @class BBCombine + * @brief Perform the basic block combination pass. + */ +class BBCombine : public PassME { + public: + BBCombine() : PassME("BBCombine", kPreOrderDFSTraversal, "5_post_bbcombine_cfg") { + } + + bool Gate(const PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + return ((cUnit->disable_opt & (1 << kSuppressExceptionEdges)) != 0); + } + + bool Worker(const PassDataHolder* data) const; +}; + +/** + * @class BasicBlock Optimizations + * @brief Any simple BasicBlock optimization can be put here. + */ +class BBOptimizations : public PassME { + public: + BBOptimizations() : PassME("BBOptimizations", kNoNodes, "5_post_bbo_cfg") { + } + + bool Gate(const PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* cUnit = down_cast(data)->c_unit; + DCHECK(cUnit != nullptr); + return ((cUnit->disable_opt & (1 << kBBOpt)) == 0); + } + + void Start(PassDataHolder* data) const; +}; + +// dummy pass, for placeholder only +class DummyPass : public PassME { + public: + DummyPass() : PassME("DummyPass", kNoNodes, "") { + } + + bool Gate(const PassDataHolder* data) const { + return false; + } +}; + +} // namespace art + +#endif // ART_COMPILER_DEX_BB_OPTIMIZATIONS_H_ diff --git a/compiler/dex/compiler_enums.h b/compiler/dex/compiler_enums.h index 97a682f2aa9..62be1f3fc7d 100644 --- a/compiler/dex/compiler_enums.h +++ b/compiler/dex/compiler_enums.h @@ -22,11 +22,22 @@ namespace art { enum RegisterClass { + kInvalidRegClass, kCoreReg, kFPReg, + kRefReg, kAnyReg, }; +enum BitsUsed { + kSize32Bits, + kSize64Bits, + kSize128Bits, + kSize256Bits, + kSize512Bits, + kSize1024Bits, +}; + enum SpecialTargetRegister { kSelf, // Thread pointer. kSuspend, // Used to reduce suspend checks for some targets. @@ -37,13 +48,23 @@ enum SpecialTargetRegister { kArg1, kArg2, kArg3, + kArg4, + kArg5, + kArg6, + kArg7, kFArg0, kFArg1, kFArg2, kFArg3, + kFArg4, + kFArg5, + kFArg6, + kFArg7, kRet0, kRet1, kInvokeTgt, + kHiddenArg, + kHiddenFpArg, kCount }; @@ -55,6 +76,7 @@ enum RegLocationType { }; enum BBType { + kNullBlock, kEntryBlock, kDalvikByteCode, kExitBlock, @@ -62,22 +84,6 @@ enum BBType { 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, @@ -114,20 +120,131 @@ enum ExtendedMIROpcode { kMirOpCheck, kMirOpCheckPart2, kMirOpSelect, + + // Vector opcodes: + // TypeSize is an encoded field giving the element type and the vector size. + // It is encoded as OpSize << 16 | (number of bits in vector) + // + // Destination and source are integers that will be interpreted by the + // backend that supports Vector operations. Backends are permitted to support only + // certain vector register sizes. + // + // At this point, only two operand instructions are supported. Three operand instructions + // could be supported by using a bit in TypeSize and arg[0] where needed. + + // @brief MIR to move constant data to a vector register + // vA: destination + // vB: number of bits in register + // args[0]~args[3]: up to 128 bits of data for initialization + kMirOpConstVector, + + // @brief MIR to move a vectorized register to another + // vA: destination + // vB: source + // vC: TypeSize + kMirOpMoveVector, + + // @brief Packed multiply of units in two vector registers: vB = vB .* vC using vA to know the type of the vector. + // vA: destination and source + // vB: source + // vC: TypeSize + kMirOpPackedMultiply, + + // @brief Packed addition of units in two vector registers: vB = vB .+ vC using vA to know the type of the vector. + // vA: destination and source + // vB: source + // vC: TypeSize + kMirOpPackedAddition, + + // @brief Packed subtraction of units in two vector registers: vB = vB .- vC using vA to know the type of the vector. + // vA: destination and source + // vB: source + // vC: TypeSize + kMirOpPackedSubtract, + + // @brief Packed shift left of units in two vector registers: vB = vB .<< vC using vA to know the type of the vector. + // vA: destination and source + // vB: amount to shift + // vC: TypeSize + kMirOpPackedShiftLeft, + + // @brief Packed signed shift right of units in two vector registers: vB = vB .>> vC using vA to know the type of the vector. + // vA: destination and source + // vB: amount to shift + // vC: TypeSize + kMirOpPackedSignedShiftRight, + + // @brief Packed unsigned shift right of units in two vector registers: vB = vB .>>> vC using vA to know the type of the vector. + // vA: destination and source + // vB: amount to shift + // vC: TypeSize + kMirOpPackedUnsignedShiftRight, + + // @brief Packed bitwise and of units in two vector registers: vB = vB .& vC using vA to know the type of the vector. + // vA: destination and source + // vB: source + // vC: TypeSize + kMirOpPackedAnd, + + // @brief Packed bitwise or of units in two vector registers: vB = vB .| vC using vA to know the type of the vector. + // vA: destination and source + // vB: source + // vC: TypeSize + kMirOpPackedOr, + + // @brief Packed bitwise xor of units in two vector registers: vB = vB .^ vC using vA to know the type of the vector. + // vA: destination and source + // vB: source + // vC: TypeSize + kMirOpPackedXor, + + // @brief Reduce a 128-bit packed element into a single VR by taking lower bits + // @details Instruction does a horizontal addition of the packed elements and then adds it to VR + // vA: destination and source VR (not vector register) + // vB: source (vector register) + // vC: TypeSize + kMirOpPackedAddReduce, + + // @brief Extract a packed element into a single VR. + // vA: destination VR (not vector register) + // vB: source (vector register) + // vC: TypeSize + // arg[0]: The index to use for extraction from vector register (which packed element) + kMirOpPackedReduce, + + // @brief Create a vector value, with all TypeSize values equal to vC + // vA: destination vector register + // vB: source VR (not vector register) + // vC: TypeSize + kMirOpPackedSet, + + // @brief Reserve N vector registers (named 0..N-1) + // vA: Number of registers + // @note: The backend may choose to map vector numbers used in vector opcodes. + // Reserved registers are removed from the list of backend temporary pool. + kMirOpReserveVectorRegisters, + + // @brief Free Reserved vector registers + // @note: All currently reserved vector registers are returned to the temporary pool. + kMirOpReturnVectorRegisters, + kMirOpLast, }; -enum MIROptimizationFlagPositons { +enum MIROptimizationFlagPositions { kMIRIgnoreNullCheck = 0, kMIRNullCheckOnly, kMIRIgnoreRangeCheck, kMIRRangeCheckOnly, + kMIRIgnoreClInitCheck, kMIRInlined, // Invoke is inlined (ie dead). kMIRInlinedPred, // Invoke is inlined via prediction. kMIRCallee, // Instruction is inlined from callee. kMIRIgnoreSuspendCheck, + kMIRIgnoreZeroDivCheck, kMIRDup, kMIRMark, // Temporary node mark. + kMIRLastMIRFlag, }; // For successor_block_list. @@ -144,8 +261,10 @@ enum AssemblerStatus { }; enum OpSize { - kWord, - kLong, + kWord, // Natural word size of target (32/64). + k32, + k64, + kReference, // Object reference; compressed on 64-bit targets. kSingle, kDouble, kUnsignedHalf, @@ -158,6 +277,7 @@ std::ostream& operator<<(std::ostream& os, const OpSize& kind); enum OpKind { kOpMov, + kOpCmov, kOpMvn, kOpCmp, kOpLsl, @@ -180,6 +300,8 @@ enum OpKind { kOpBic, kOpCmn, kOpTst, + kOpRev, + kOpRevsh, kOpBkpt, kOpBlx, kOpPush, @@ -193,15 +315,31 @@ enum OpKind { kOpInvalid, }; +enum MoveType { + kMov8GP, // Move 8-bit general purpose register. + kMov16GP, // Move 16-bit general purpose register. + kMov32GP, // Move 32-bit general purpose register. + kMov64GP, // Move 64-bit general purpose register. + kMov32FP, // Move 32-bit FP register. + kMov64FP, // Move 64-bit FP register. + kMovLo64FP, // Move low 32-bits of 64-bit FP register. + kMovHi64FP, // Move high 32-bits of 64-bit FP register. + kMovU128FP, // Move 128-bit FP register to/from possibly unaligned region. + kMov128FP = kMovU128FP, + kMovA128FP, // Move 128-bit FP register to/from region surely aligned to 16-bytes. + kMovLo128FP, // Move low 64-bits of 128-bit FP register. + kMovHi128FP, // Move high 64-bits of 128-bit FP register. +}; + 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, + kCondCs, // carry set + kCondCc, // carry clear + kCondUlt, // unsigned less than + kCondUge, // unsigned greater than or same kCondMi, // minus kCondPl, // plus, positive or zero kCondVs, // overflow @@ -288,36 +426,6 @@ enum X86ConditionCode { 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, @@ -327,12 +435,25 @@ enum DividePattern { std::ostream& operator<<(std::ostream& os, const DividePattern& pattern); -// Memory barrier types (see "The JSR-133 Cookbook for Compiler Writers"). +/** + * @brief Memory barrier types (see "The JSR-133 Cookbook for Compiler Writers"). + * @details We define the combined barrier types that are actually required + * by the Java Memory Model, rather than using exactly the terminology from + * the JSR-133 cookbook. These should, in many cases, be replaced by acquire/release + * primitives. Note that the JSR-133 cookbook generally does not deal with + * store atomicity issues, and the recipes there are not always entirely sufficient. + * The current recipe is as follows: + * -# Use AnyStore ~= (LoadStore | StoreStore) ~= release barrier before volatile store. + * -# Use AnyAny barrier after volatile store. (StoreLoad is as expensive.) + * -# Use LoadAny barrier ~= (LoadLoad | LoadStore) ~= acquire barrierafter each volatile load. + * -# Use StoreStore barrier after all stores but before return from any constructor whose + * class has final fields. + */ enum MemBarrierKind { - kLoadStore, - kLoadLoad, + kAnyStore, + kLoadAny, kStoreStore, - kStoreLoad + kAnyAny }; std::ostream& operator<<(std::ostream& os, const MemBarrierKind& kind); @@ -347,11 +468,17 @@ enum OpFeatureFlags { kIsQuinOp, kIsSextupleOp, kIsIT, + kIsMoveOp, kMemLoad, kMemStore, + kMemVolatile, + kMemScaledx0, + kMemScaledx2, + kMemScaledx4, kPCRelFixup, // x86 FIXME: add NEEDS_FIXUP to instruction attributes. kRegDef0, kRegDef1, + kRegDef2, kRegDefA, kRegDefD, kRegDefFPCSList0, @@ -369,6 +496,7 @@ enum OpFeatureFlags { kRegUseA, kRegUseC, kRegUseD, + kRegUseB, kRegUseFPCSList0, kRegUseFPCSList2, kRegUseList0, @@ -377,7 +505,12 @@ enum OpFeatureFlags { kRegUsePC, kRegUseSP, kSetsCCodes, - kUsesCCodes + kUsesCCodes, + kUseFpStack, + kUseHi, + kUseLo, + kDefHi, + kDefLo }; enum SelectInstructionKind { @@ -389,28 +522,41 @@ enum SelectInstructionKind { 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 +// LIR fixup kinds for Arm +enum FixupKind { + kFixupNone, + kFixupLabel, // For labels we just adjust the offset. + kFixupLoad, // Mostly for immediates. + kFixupVLoad, // FP load which *may* be pc-relative. + kFixupCBxZ, // Cbz, Cbnz. + kFixupPushPop, // Not really pc relative, but changes size based on args. + kFixupCondBranch, // Conditional branch + kFixupT1Branch, // Thumb1 Unconditional branch + kFixupT2Branch, // Thumb2 Unconditional branch + kFixupBlx1, // Blx1 (start of Blx1/Blx2 pair). + kFixupBl1, // Bl1 (start of Bl1/Bl2 pair). + kFixupAdr, // Adr. + kFixupMovImmLST, // kThumb2MovImm16LST. + kFixupMovImmHST, // kThumb2MovImm16HST. + kFixupAlign4, // Align to 4-byte boundary. +}; + +std::ostream& operator<<(std::ostream& os, const FixupKind& kind); + +enum VolatileKind { + kNotVolatile, // Load/Store is not volatile + kVolatile // Load/Store is volatile +}; + +std::ostream& operator<<(std::ostream& os, const VolatileKind& kind); + +enum WideKind { + kNotWide, // Non-wide view + kWide, // Wide view + kRef // Ref width }; -std::ostream& operator<<(std::ostream& os, const OatBitMapKind& kind); +std::ostream& operator<<(std::ostream& os, const WideKind& kind); } // namespace art diff --git a/compiler/dex/compiler_ir.h b/compiler/dex/compiler_ir.h index 6607562b134..66fb608d391 100644 --- a/compiler/dex/compiler_ir.h +++ b/compiler/dex/compiler_ir.h @@ -18,57 +18,33 @@ #define ART_COMPILER_DEX_COMPILER_IR_H_ #include -#include -#include "arena_allocator.h" -#include "backend.h" + #include "compiler_enums.h" #include "dex/quick/mir_to_lir.h" #include "dex_instruction.h" #include "driver/compiler_driver.h" #include "driver/dex_compilation_unit.h" -#include "llvm/intrinsic_helper.h" -#include "llvm/ir_builder.h" #include "safe_map.h" +#include "utils/scoped_arena_allocator.h" +#include "base/timing_logger.h" +#include "utils/arena_allocator.h" namespace art { -class LLVMInfo; -namespace llvm { -class LlvmCompilationUnit; -} // namespace llvm - struct ArenaMemBlock; +class Backend; struct Memstats; class MIRGraph; class Mir2Lir; struct CompilationUnit { - explicit CompilationUnit(ArenaPool* pool) - : 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), - arena(pool), - mir_graph(NULL), - cg(NULL) {} + explicit CompilationUnit(ArenaPool* pool); + ~CompilationUnit(); + + void StartTimingSplit(const char* label); + void NewTimingSplit(const char* label); + void EndTiming(); + /* * Fields needed/generated by common frontend and generally used throughout * the compiler. @@ -86,18 +62,19 @@ struct CompilationUnit { uint32_t disable_opt; // opt_control_vector flags. uint32_t enable_debug; // debugControlVector flags. bool verbose; - CompilerBackend compiler_backend; + const Compiler* compiler; InstructionSet instruction_set; + bool target64; + InstructionSetFeatures GetInstructionSetFeatures() { + return compiler_driver->GetInstructionSetFeatures(); + } // TODO: much of this info available elsewhere. Go to the original source? - int num_dalvik_registers; // method->registers_size. + uint16_t 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; + uint16_t num_ins; + uint16_t num_outs; + uint16_t num_regs; // Unlike num_dalvik_registers, does not include ins. // If non-empty, apply optimizer/debug flags only to matching methods. std::string compiler_method_match; @@ -106,9 +83,12 @@ struct CompilationUnit { // TODO: move memory management to mir_graph, or just switch to using standard containers. ArenaAllocator arena; + ArenaStack arena_stack; // Arenas for ScopedArenaAllocator. - UniquePtr mir_graph; // MIR container. - UniquePtr cg; // Target-specific codegen. + std::unique_ptr mir_graph; // MIR container. + std::unique_ptr cg; // Target-specific codegen. + TimingLogger timings; + bool print_pass; // Do we want to print a pass or not? }; } // namespace art diff --git a/compiler/dex/dataflow_iterator-inl.h b/compiler/dex/dataflow_iterator-inl.h index 06cc505a9ae..d1abf7fa126 100644 --- a/compiler/dex/dataflow_iterator-inl.h +++ b/compiler/dex/dataflow_iterator-inl.h @@ -21,48 +21,156 @@ namespace art { -inline BasicBlock* DataflowIterator::NextBody(bool had_change) { - changed_ |= had_change; +// Single forward pass over the nodes. +inline BasicBlock* DataflowIterator::ForwardSingleNext() { 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); - } + + // Are we not yet at the end? + if (idx_ < end_idx_) { + // Get the next index. + BasicBlockId bb_id = block_id_list_->Get(idx_); + res = mir_graph_->GetBasicBlock(bb_id); + idx_++; } + return res; } -// AllNodes uses the existing GrowableArray iterator, so use different NextBody(). -inline BasicBlock* AllNodesIterator::NextBody(bool had_change) { - changed_ |= had_change; +// Repeat full forward passes over all nodes until no change occurs during a complete pass. +inline BasicBlock* DataflowIterator::ForwardRepeatNext() { + BasicBlock* res = NULL; + + // Are we at the end and have we changed something? + if ((idx_ >= end_idx_) && changed_ == true) { + // Reset the index. + idx_ = start_idx_; + repeats_++; + changed_ = false; + } + + // Are we not yet at the end? + if (idx_ < end_idx_) { + // Get the BasicBlockId. + BasicBlockId bb_id = block_id_list_->Get(idx_); + res = mir_graph_->GetBasicBlock(bb_id); + idx_++; + } + + return res; +} + +// Single reverse pass over the nodes. +inline BasicBlock* DataflowIterator::ReverseSingleNext() { + BasicBlock* res = NULL; + + // Are we not yet at the end? + if (idx_ >= 0) { + // Get the BasicBlockId. + BasicBlockId bb_id = block_id_list_->Get(idx_); + res = mir_graph_->GetBasicBlock(bb_id); + idx_--; + } + + return res; +} + +// Repeat full backwards passes over all nodes until no change occurs during a complete pass. +inline BasicBlock* DataflowIterator::ReverseRepeatNext() { + BasicBlock* res = NULL; + + // Are we done and we changed something during the last iteration? + if ((idx_ < 0) && changed_) { + // Reset the index. + idx_ = start_idx_; + repeats_++; + changed_ = false; + } + + // Are we not yet done? + if (idx_ >= 0) { + // Get the BasicBlockId. + BasicBlockId bb_id = block_id_list_->Get(idx_); + res = mir_graph_->GetBasicBlock(bb_id); + idx_--; + } + + return res; +} + +// AllNodes uses the existing GrowableArray iterator, and should be considered unordered. +inline BasicBlock* AllNodesIterator::Next(bool had_change) { BasicBlock* res = NULL; + + // Suppose we want to keep looking. 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)) { + + // Find the next BasicBlock. + while (keep_looking == true) { + // Get next BasicBlock. + res = all_nodes_iterator_.Next(); + + // Are we done or is the BasicBlock not hidden? + if ((res == NULL) || (res->hidden == false)) { keep_looking = false; } } + + // Update changed: if had_changed is true, we remember it for the whole iteration. + changed_ |= had_change; + return res; } +inline BasicBlock* LoopRepeatingTopologicalSortIterator::Next(bool had_change) { + if (idx_ != 0) { + // Mark last processed block visited. + BasicBlock* bb = mir_graph_->GetBasicBlock(block_id_list_->Get(idx_ - 1)); + bb->visited = true; + if (had_change) { + // If we had a change we need to revisit the children. + ChildBlockIterator iter(bb, mir_graph_); + for (BasicBlock* child_bb = iter.Next(); child_bb != nullptr; child_bb = iter.Next()) { + child_bb->visited = false; + } + } + } + + while (true) { + // Pop loops we have left and check if we need to recalculate one of them. + // NOTE: We need to do this even if idx_ == end_idx_. + while (loop_head_stack_->Size() != 0u && + loop_ends_->Get(loop_head_stack_->Peek().first) == idx_) { + auto top = loop_head_stack_->Peek(); + uint16_t loop_head_idx = top.first; + bool recalculated = top.second; + loop_head_stack_->Pop(); + BasicBlock* loop_head = mir_graph_->GetBasicBlock(block_id_list_->Get(loop_head_idx)); + DCHECK(loop_head != nullptr); + if (!recalculated || !loop_head->visited) { + loop_head_stack_->Insert(std::make_pair(loop_head_idx, true)); // Recalculating this loop. + idx_ = loop_head_idx + 1; + return loop_head; + } + } + + if (idx_ == end_idx_) { + return nullptr; + } + + // Get next block and return it if unvisited. + BasicBlockId idx = idx_; + idx_ += 1; + BasicBlock* bb = mir_graph_->GetBasicBlock(block_id_list_->Get(idx)); + DCHECK(bb != nullptr); + if (!bb->visited) { + if (loop_ends_->Get(idx) != 0u) { + loop_head_stack_->Insert(std::make_pair(idx, false)); // Not recalculating. + } + return bb; + } + } +} + } // namespace art #endif // ART_COMPILER_DEX_DATAFLOW_ITERATOR_INL_H_ diff --git a/compiler/dex/dataflow_iterator.h b/compiler/dex/dataflow_iterator.h index da44ffd99c5..06d6832340e 100644 --- a/compiler/dex/dataflow_iterator.h +++ b/compiler/dex/dataflow_iterator.h @@ -27,127 +27,412 @@ namespace art { * 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. + * necessitating another pass over the list. If this behavior is required, use the + * "Repeating" variant. For the repeating variant, the caller must tell the iterator + * whether a change has been made that necessitates another pass. Note that calling Next(true) + * does not affect the iteration order or short-circuit 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 + * @brief The main iterator class, all other iterators derive of this one to define an iteration order. */ class DataflowIterator { public: virtual ~DataflowIterator() {} - // Return the next BasicBlock* to visit. - BasicBlock* Next() { - DCHECK(!is_iterative_); - return NextBody(false); - } + /** + * @brief How many times have we repeated the iterator across the BasicBlocks? + * @return the number of iteration repetitions. + */ + int32_t GetRepeatCount() { return repeats_; } - /* - * Return the next BasicBlock* to visit, and tell the iterator whether any change - * has occurred that requires another full pass over the block list. + /** + * @brief Has the user of the iterator reported a change yet? + * @details Does not mean there was or not a change, it is only whether the user passed a true to the Next function call. + * @return whether the user of the iterator reported a change yet. */ - BasicBlock* Next(bool had_change) { - DCHECK(is_iterative_); - return NextBody(had_change); - } + int32_t GetChanged() { return changed_; } + + /** + * @brief Get the next BasicBlock depending on iteration order. + * @param had_change did the user of the iteration change the previous BasicBlock. + * @return the next BasicBlock following the iteration order, 0 if finished. + */ + virtual BasicBlock* Next(bool had_change = false) = 0; protected: - DataflowIterator(MIRGraph* mir_graph, bool is_iterative, int start_idx, int end_idx, - bool reverse) + /** + * @param mir_graph the MIRGraph we are interested in. + * @param start_idx the first index we want to iterate across. + * @param end_idx the last index we want to iterate (not included). + */ + DataflowIterator(MIRGraph* mir_graph, int32_t start_idx, int32_t end_idx) : mir_graph_(mir_graph), - is_iterative_(is_iterative), start_idx_(start_idx), end_idx_(end_idx), - reverse_(reverse), block_id_list_(NULL), idx_(0), + repeats_(0), changed_(false) {} - virtual BasicBlock* NextBody(bool had_change) ALWAYS_INLINE; + /** + * @brief Get the next BasicBlock iterating forward. + * @return the next BasicBlock iterating forward. + */ + virtual BasicBlock* ForwardSingleNext() ALWAYS_INLINE; + + /** + * @brief Get the next BasicBlock iterating backward. + * @return the next BasicBlock iterating backward. + */ + virtual BasicBlock* ReverseSingleNext() ALWAYS_INLINE; - 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_; + /** + * @brief Get the next BasicBlock iterating forward, restart if a BasicBlock was reported changed during the last iteration. + * @return the next BasicBlock iterating forward, with chance of repeating the iteration. + */ + virtual BasicBlock* ForwardRepeatNext() ALWAYS_INLINE; + + /** + * @brief Get the next BasicBlock iterating backward, restart if a BasicBlock was reported changed during the last iteration. + * @return the next BasicBlock iterating backward, with chance of repeating the iteration. + */ + virtual BasicBlock* ReverseRepeatNext() ALWAYS_INLINE; + + MIRGraph* const mir_graph_; /**< @brief the MIRGraph */ + const int32_t start_idx_; /**< @brief the start index for the iteration */ + const int32_t end_idx_; /**< @brief the last index for the iteration */ + GrowableArray* block_id_list_; /**< @brief the list of BasicBlocks we want to iterate on */ + int32_t idx_; /**< @brief Current index for the iterator */ + int32_t repeats_; /**< @brief Number of repeats over the iteration */ + bool changed_; /**< @brief Has something changed during the current iteration? */ }; // DataflowIterator - class ReachableNodesIterator : public DataflowIterator { + /** + * @class PreOrderDfsIterator + * @brief Used to perform a Pre-order Depth-First-Search Iteration of a MIRGraph. + */ + class PreOrderDfsIterator : public DataflowIterator { public: - ReachableNodesIterator(MIRGraph* mir_graph, bool is_iterative) - : DataflowIterator(mir_graph, is_iterative, 0, - mir_graph->GetNumReachableBlocks(), false) { + /** + * @brief The constructor, using all of the reachable blocks of the MIRGraph. + * @param mir_graph The MIRGraph considered. + */ + explicit PreOrderDfsIterator(MIRGraph* mir_graph) + : DataflowIterator(mir_graph, 0, mir_graph->GetNumReachableBlocks()) { + // Extra setup for the PreOrderDfsIterator. idx_ = start_idx_; block_id_list_ = mir_graph->GetDfsOrder(); } + + /** + * @brief Get the next BasicBlock depending on iteration order. + * @param had_change did the user of the iteration change the previous BasicBlock. + * @return the next BasicBlock following the iteration order, 0 if finished. + */ + virtual BasicBlock* Next(bool had_change = false) { + // Update changed: if had_changed is true, we remember it for the whole iteration. + changed_ |= had_change; + + return ForwardSingleNext(); + } }; - class PreOrderDfsIterator : public DataflowIterator { + /** + * @class RepeatingPreOrderDfsIterator + * @brief Used to perform a Repeating Pre-order Depth-First-Search Iteration of a MIRGraph. + * @details If there is a change during an iteration, the iteration starts over at the end of the iteration. + */ + class RepeatingPreOrderDfsIterator : public DataflowIterator { public: - PreOrderDfsIterator(MIRGraph* mir_graph, bool is_iterative) - : DataflowIterator(mir_graph, is_iterative, 0, - mir_graph->GetNumReachableBlocks(), false) { + /** + * @brief The constructor, using all of the reachable blocks of the MIRGraph. + * @param mir_graph The MIRGraph considered. + */ + explicit RepeatingPreOrderDfsIterator(MIRGraph* mir_graph) + : DataflowIterator(mir_graph, 0, mir_graph->GetNumReachableBlocks()) { + // Extra setup for the RepeatingPreOrderDfsIterator. idx_ = start_idx_; block_id_list_ = mir_graph->GetDfsOrder(); } + + /** + * @brief Get the next BasicBlock depending on iteration order. + * @param had_change did the user of the iteration change the previous BasicBlock. + * @return the next BasicBlock following the iteration order, 0 if finished. + */ + virtual BasicBlock* Next(bool had_change = false) { + // Update changed: if had_changed is true, we remember it for the whole iteration. + changed_ |= had_change; + + return ForwardRepeatNext(); + } }; - class PostOrderDfsIterator : public DataflowIterator { + /** + * @class RepeatingPostOrderDfsIterator + * @brief Used to perform a Repeating Post-order Depth-First-Search Iteration of a MIRGraph. + * @details If there is a change during an iteration, the iteration starts over at the end of the iteration. + */ + class RepeatingPostOrderDfsIterator : public DataflowIterator { public: - PostOrderDfsIterator(MIRGraph* mir_graph, bool is_iterative) - : DataflowIterator(mir_graph, is_iterative, 0, - mir_graph->GetNumReachableBlocks(), false) { + /** + * @brief The constructor, using all of the reachable blocks of the MIRGraph. + * @param mir_graph The MIRGraph considered. + */ + explicit RepeatingPostOrderDfsIterator(MIRGraph* mir_graph) + : DataflowIterator(mir_graph, 0, mir_graph->GetNumReachableBlocks()) { + // Extra setup for the RepeatingPostOrderDfsIterator. idx_ = start_idx_; block_id_list_ = mir_graph->GetDfsPostOrder(); } + + /** + * @brief Get the next BasicBlock depending on iteration order. + * @param had_change did the user of the iteration change the previous BasicBlock. + * @return the next BasicBlock following the iteration order, 0 if finished. + */ + virtual BasicBlock* Next(bool had_change = false) { + // Update changed: if had_changed is true, we remember it for the whole iteration. + changed_ |= had_change; + + return ForwardRepeatNext(); + } }; + /** + * @class ReversePostOrderDfsIterator + * @brief Used to perform a Reverse Post-order Depth-First-Search Iteration of a MIRGraph. + */ class ReversePostOrderDfsIterator : public DataflowIterator { public: - ReversePostOrderDfsIterator(MIRGraph* mir_graph, bool is_iterative) - : DataflowIterator(mir_graph, is_iterative, - mir_graph->GetNumReachableBlocks() -1, 0, true) { + /** + * @brief The constructor, using all of the reachable blocks of the MIRGraph. + * @param mir_graph The MIRGraph considered. + */ + explicit ReversePostOrderDfsIterator(MIRGraph* mir_graph) + : DataflowIterator(mir_graph, mir_graph->GetNumReachableBlocks() -1, 0) { + // Extra setup for the ReversePostOrderDfsIterator. idx_ = start_idx_; block_id_list_ = mir_graph->GetDfsPostOrder(); } + + /** + * @brief Get the next BasicBlock depending on iteration order. + * @param had_change did the user of the iteration change the previous BasicBlock. + * @return the next BasicBlock following the iteration order, 0 if finished. + */ + virtual BasicBlock* Next(bool had_change = false) { + // Update changed: if had_changed is true, we remember it for the whole iteration. + changed_ |= had_change; + + return ReverseSingleNext(); + } }; + /** + * @class ReversePostOrderDfsIterator + * @brief Used to perform a Repeating Reverse Post-order Depth-First-Search Iteration of a MIRGraph. + * @details If there is a change during an iteration, the iteration starts over at the end of the iteration. + */ + class RepeatingReversePostOrderDfsIterator : public DataflowIterator { + public: + /** + * @brief The constructor, using all of the reachable blocks of the MIRGraph. + * @param mir_graph The MIRGraph considered. + */ + explicit RepeatingReversePostOrderDfsIterator(MIRGraph* mir_graph) + : DataflowIterator(mir_graph, mir_graph->GetNumReachableBlocks() -1, 0) { + // Extra setup for the RepeatingReversePostOrderDfsIterator + idx_ = start_idx_; + block_id_list_ = mir_graph->GetDfsPostOrder(); + } + + /** + * @brief Get the next BasicBlock depending on iteration order. + * @param had_change did the user of the iteration change the previous BasicBlock. + * @return the next BasicBlock following the iteration order, 0 if finished. + */ + virtual BasicBlock* Next(bool had_change = false) { + // Update changed: if had_changed is true, we remember it for the whole iteration. + changed_ |= had_change; + + return ReverseRepeatNext(); + } + }; + + /** + * @class PostOrderDOMIterator + * @brief Used to perform a Post-order Domination Iteration of a MIRGraph. + */ class PostOrderDOMIterator : public DataflowIterator { public: - PostOrderDOMIterator(MIRGraph* mir_graph, bool is_iterative) - : DataflowIterator(mir_graph, is_iterative, 0, - mir_graph->GetNumReachableBlocks(), false) { + /** + * @brief The constructor, using all of the reachable blocks of the MIRGraph. + * @param mir_graph The MIRGraph considered. + */ + explicit PostOrderDOMIterator(MIRGraph* mir_graph) + : DataflowIterator(mir_graph, 0, mir_graph->GetNumReachableBlocks()) { + // Extra setup for thePostOrderDOMIterator. idx_ = start_idx_; block_id_list_ = mir_graph->GetDomPostOrder(); } + + /** + * @brief Get the next BasicBlock depending on iteration order. + * @param had_change did the user of the iteration change the previous BasicBlock. + * @return the next BasicBlock following the iteration order, 0 if finished. + */ + virtual BasicBlock* Next(bool had_change = false) { + // Update changed: if had_changed is true, we remember it for the whole iteration. + changed_ |= had_change; + + return ForwardSingleNext(); + } }; + /** + * @class AllNodesIterator + * @brief Used to perform an iteration on all the BasicBlocks a MIRGraph. + */ 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()); + /** + * @brief The constructor, using all of the reachable blocks of the MIRGraph. + * @param mir_graph The MIRGraph considered. + */ + explicit AllNodesIterator(MIRGraph* mir_graph) + : DataflowIterator(mir_graph, 0, 0), + all_nodes_iterator_(mir_graph->GetBlockList()) { } + /** + * @brief Resetting the iterator. + */ void Reset() { - all_nodes_iterator_->Reset(); + all_nodes_iterator_.Reset(); + } + + /** + * @brief Get the next BasicBlock depending on iteration order. + * @param had_change did the user of the iteration change the previous BasicBlock. + * @return the next BasicBlock following the iteration order, 0 if finished. + */ + virtual BasicBlock* Next(bool had_change = false) ALWAYS_INLINE; + + private: + GrowableArray::Iterator all_nodes_iterator_; /**< @brief The list of all the nodes */ + }; + + /** + * @class TopologicalSortIterator + * @brief Used to perform a Topological Sort Iteration of a MIRGraph. + */ + class TopologicalSortIterator : public DataflowIterator { + public: + /** + * @brief The constructor, using all of the reachable blocks of the MIRGraph. + * @param mir_graph The MIRGraph considered. + */ + explicit TopologicalSortIterator(MIRGraph* mir_graph) + : DataflowIterator(mir_graph, 0, mir_graph->GetTopologicalSortOrder()->Size()) { + // Extra setup for TopologicalSortIterator. + idx_ = start_idx_; + block_id_list_ = mir_graph->GetTopologicalSortOrder(); } - BasicBlock* NextBody(bool had_change) ALWAYS_INLINE; + /** + * @brief Get the next BasicBlock depending on iteration order. + * @param had_change did the user of the iteration change the previous BasicBlock. + * @return the next BasicBlock following the iteration order, 0 if finished. + */ + virtual BasicBlock* Next(bool had_change = false) { + // Update changed: if had_changed is true, we remember it for the whole iteration. + changed_ |= had_change; + + return ForwardSingleNext(); + } + }; + + /** + * @class RepeatingTopologicalSortIterator + * @brief Used to perform a Topological Sort Iteration of a MIRGraph. + * @details If there is a change during an iteration, the iteration starts over at the end of the + * iteration. + */ + class RepeatingTopologicalSortIterator : public DataflowIterator { + public: + /** + * @brief The constructor, using all of the reachable blocks of the MIRGraph. + * @param mir_graph The MIRGraph considered. + */ + explicit RepeatingTopologicalSortIterator(MIRGraph* mir_graph) + : DataflowIterator(mir_graph, 0, mir_graph->GetTopologicalSortOrder()->Size()) { + // Extra setup for RepeatingTopologicalSortIterator. + idx_ = start_idx_; + block_id_list_ = mir_graph->GetTopologicalSortOrder(); + } + + /** + * @brief Get the next BasicBlock depending on iteration order. + * @param had_change did the user of the iteration change the previous BasicBlock. + * @return the next BasicBlock following the iteration order, 0 if finished. + */ + virtual BasicBlock* Next(bool had_change = false) { + // Update changed: if had_changed is true, we remember it for the whole iteration. + changed_ |= had_change; + + return ForwardRepeatNext(); + } + }; + + /** + * @class LoopRepeatingTopologicalSortIterator + * @brief Used to perform a Topological Sort Iteration of a MIRGraph, repeating loops as needed. + * @details The iterator uses the visited flags to keep track of the blocks that need + * recalculation and keeps a stack of loop heads in the MIRGraph. At the end of the loop + * it returns back to the loop head if it needs to be recalculated. Due to the use of + * the visited flags and the loop head stack in the MIRGraph, it's not possible to use + * two iterators at the same time or modify this data during iteration (though inspection + * of this data is allowed and sometimes even expected). + * + * NOTE: This iterator is not suitable for passes that need to propagate changes to + * predecessors, such as type inferrence. + */ + class LoopRepeatingTopologicalSortIterator : public DataflowIterator { + public: + /** + * @brief The constructor, using all of the reachable blocks of the MIRGraph. + * @param mir_graph The MIRGraph considered. + */ + explicit LoopRepeatingTopologicalSortIterator(MIRGraph* mir_graph) + : DataflowIterator(mir_graph, 0, mir_graph->GetTopologicalSortOrder()->Size()), + loop_ends_(mir_graph->GetTopologicalSortOrderLoopEnds()), + loop_head_stack_(mir_graph_->GetTopologicalSortOrderLoopHeadStack()) { + // Extra setup for RepeatingTopologicalSortIterator. + idx_ = start_idx_; + block_id_list_ = mir_graph->GetTopologicalSortOrder(); + // Clear visited flags and check that the loop head stack is empty. + mir_graph->ClearAllVisitedFlags(); + DCHECK_EQ(loop_head_stack_->Size(), 0u); + } + + ~LoopRepeatingTopologicalSortIterator() { + DCHECK_EQ(loop_head_stack_->Size(), 0u); + } + + /** + * @brief Get the next BasicBlock depending on iteration order. + * @param had_change did the user of the iteration change the previous BasicBlock. + * @return the next BasicBlock following the iteration order, 0 if finished. + */ + virtual BasicBlock* Next(bool had_change = false) OVERRIDE; private: - GrowableArray::Iterator* all_nodes_iterator_; + const GrowableArray* const loop_ends_; + GrowableArray>* const loop_head_stack_; }; } // namespace art diff --git a/compiler/dex/dex_to_dex_compiler.cc b/compiler/dex/dex_to_dex_compiler.cc index 63d8aa04f84..b9f9437c959 100644 --- a/compiler/dex/dex_to_dex_compiler.cc +++ b/compiler/dex/dex_to_dex_compiler.cc @@ -24,6 +24,7 @@ #include "mirror/art_method-inl.h" #include "mirror/class-inl.h" #include "mirror/dex_cache.h" +#include "thread-inl.h" namespace art { namespace optimizer { @@ -51,12 +52,6 @@ class DexCompiler { return *unit_.GetDexFile(); } - // TODO: since the whole compilation pipeline uses a "const DexFile", we need - // to "unconst" here. The DEX-to-DEX compiler should work on a non-const DexFile. - DexFile& GetModifiableDexFile() { - return *const_cast(unit_.GetDexFile()); - } - bool PerformOptimizations() const { return dex_to_dex_compilation_level_ >= kOptimize; } @@ -181,8 +176,7 @@ Instruction* DexCompiler::CompileCheckCast(Instruction* inst, uint32_t dex_pc) { if (!kEnableCheckCastEllision || !PerformOptimizations()) { return inst; } - MethodReference referrer(&GetDexFile(), unit_.GetDexMethodIndex()); - if (!driver_.IsSafeCast(referrer, dex_pc)) { + if (!driver_.IsSafeCast(&unit_, dex_pc)) { return inst; } // Ok, this is a safe cast. Since the "check-cast" instruction size is 2 code @@ -214,21 +208,21 @@ void DexCompiler::CompileInstanceFieldAccess(Instruction* inst, return; } uint32_t field_idx = inst->VRegC_22c(); - int field_offset; + MemberOffset field_offset(0u); bool is_volatile; - bool fast_path = driver_.ComputeInstanceFieldInfo(field_idx, &unit_, field_offset, - is_volatile, is_put); - if (fast_path && !is_volatile && IsUint(16, field_offset)) { + bool fast_path = driver_.ComputeInstanceFieldInfo(field_idx, &unit_, is_put, + &field_offset, &is_volatile); + if (fast_path && !is_volatile && IsUint(16, field_offset.Int32Value())) { VLOG(compiler) << "Quickening " << Instruction::Name(inst->Opcode()) << " to " << Instruction::Name(new_opcode) << " by replacing field index " << field_idx - << " by field offset " << field_offset + << " by field offset " << field_offset.Int32Value() << " at dex pc " << StringPrintf("0x%x", dex_pc) << " in method " << PrettyMethod(unit_.GetDexMethodIndex(), GetDexFile(), true); // We are modifying 4 consecutive bytes. inst->SetOpcode(new_opcode); // Replace field index by field offset. - inst->SetVRegC_22c(static_cast(field_offset)); + inst->SetVRegC_22c(static_cast(field_offset.Int32Value())); } } @@ -246,11 +240,13 @@ void DexCompiler::CompileInvokeVirtual(Instruction* inst, int vtable_idx; uintptr_t direct_code; uintptr_t direct_method; - bool fast_path = driver_.ComputeInvokeInfo(&unit_, dex_pc, invoke_type, - target_method, vtable_idx, - direct_code, direct_method, - false); // TODO: support devirtualization. + const bool kEnableDevirtualization = false; + bool fast_path = driver_.ComputeInvokeInfo(&unit_, dex_pc, + false, kEnableDevirtualization, + &invoke_type, + &target_method, &vtable_idx, + &direct_code, &direct_method); if (fast_path && original_invoke_type == invoke_type) { if (vtable_idx >= 0 && IsUint(16, vtable_idx)) { VLOG(compiler) << "Quickening " << Instruction::Name(inst->Opcode()) @@ -275,15 +271,16 @@ void DexCompiler::CompileInvokeVirtual(Instruction* inst, } // namespace optimizer } // namespace art -extern "C" void ArtCompileDEX(art::CompilerDriver& compiler, const art::DexFile::CodeItem* code_item, +extern "C" void ArtCompileDEX(art::CompilerDriver& driver, const art::DexFile::CodeItem* code_item, uint32_t access_flags, art::InvokeType invoke_type, uint16_t class_def_idx, uint32_t method_idx, jobject class_loader, const art::DexFile& dex_file, art::DexToDexCompilationLevel dex_to_dex_compilation_level) { if (dex_to_dex_compilation_level != art::kDontDexToDexCompile) { art::DexCompilationUnit unit(NULL, class_loader, art::Runtime::Current()->GetClassLinker(), - dex_file, code_item, class_def_idx, method_idx, access_flags); - art::optimizer::DexCompiler dex_compiler(compiler, unit, dex_to_dex_compilation_level); + dex_file, code_item, class_def_idx, method_idx, access_flags, + driver.GetVerifiedMethod(&dex_file, method_idx)); + art::optimizer::DexCompiler dex_compiler(driver, unit, dex_to_dex_compilation_level); dex_compiler.Compile(); } } diff --git a/compiler/dex/frontend.cc b/compiler/dex/frontend.cc index fefcab9e87c..e6553f81927 100644 --- a/compiler/dex/frontend.cc +++ b/compiler/dex/frontend.cc @@ -14,61 +14,30 @@ * limitations under the License. */ -#include +#include +#include "compiler.h" #include "compiler_internals.h" #include "driver/compiler_driver.h" +#include "driver/compiler_options.h" #include "dataflow_iterator-inl.h" #include "leb128.h" #include "mirror/object.h" +#include "pass_driver_me_opts.h" #include "runtime.h" -#include "backend.h" #include "base/logging.h" - -#if defined(ART_USE_PORTABLE_COMPILER) -#include "dex/portable/mir_to_gbc.h" -#include "llvm/llvm_compilation_unit.h" -#endif - -namespace { -#if !defined(ART_USE_PORTABLE_COMPILER) - pthread_once_t llvm_multi_init = PTHREAD_ONCE_INIT; -#endif - void InitializeLLVMForQuick() { - ::llvm::llvm_start_multithreaded(); - } -} +#include "base/timing_logger.h" +#include "driver/compiler_options.h" +#include "dex/quick/dex_file_to_method_inliner_map.h" 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 ArtInitQuickCompilerContext(art::CompilerDriver* driver) { + CHECK(driver->GetCompilerContext() == nullptr); } -extern "C" void ArtUnInitQuickCompilerContext(art::CompilerDriver& compiler) { - delete reinterpret_cast(compiler.GetCompilerContext()); - compiler.SetCompilerContext(NULL); +extern "C" void ArtUnInitQuickCompilerContext(art::CompilerDriver* driver) { + CHECK(driver->GetCompilerContext() == nullptr); } /* Default optimizer/debug setting for the compiler. */ @@ -77,12 +46,16 @@ static uint32_t kCompilerOptimizerDisableFlags = 0 | // Disable specific optimi // (1 << kLoadHoisting) | // (1 << kSuppressLoads) | // (1 << kNullCheckElimination) | + // (1 << kClassInitCheckElimination) | + (1 << kGlobalValueNumbering) | // (1 << kPromoteRegs) | // (1 << kTrackLiveTemps) | // (1 << kSafeOptimizations) | // (1 << kBBOpt) | // (1 << kMatch) | // (1 << kPromoteCompilerTemps) | + // (1 << kSuppressExceptionEdges) | + (1 << kSuppressMethodInlining) | 0; static uint32_t kCompilerDebugFlags = 0 | // Enable debug/testing modes @@ -104,31 +77,590 @@ static uint32_t kCompilerDebugFlags = 0 | // Enable debug/testing modes // (1 << kDebugVerifyBitcode) | // (1 << kDebugShowSummaryMemoryUsage) | // (1 << kDebugShowFilterStats) | + // (1 << kDebugTimings) | + // (1 << kDebugCodegenDump) | 0; -static CompiledMethod* CompileMethod(CompilerDriver& compiler, - const CompilerBackend compiler_backend, +COMPILE_ASSERT(0U == static_cast(kNone), kNone_not_0); +COMPILE_ASSERT(1U == static_cast(kArm), kArm_not_1); +COMPILE_ASSERT(2U == static_cast(kArm64), kArm64_not_2); +COMPILE_ASSERT(3U == static_cast(kThumb2), kThumb2_not_3); +COMPILE_ASSERT(4U == static_cast(kX86), kX86_not_4); +COMPILE_ASSERT(5U == static_cast(kX86_64), kX86_64_not_5); +COMPILE_ASSERT(6U == static_cast(kMips), kMips_not_6); +COMPILE_ASSERT(7U == static_cast(kMips64), kMips64_not_7); + +// Additional disabled optimizations (over generally disabled) per instruction set. +static constexpr uint32_t kDisabledOptimizationsPerISA[] = { + // 0 = kNone. + ~0U, + // 1 = kArm, unused (will use kThumb2). + ~0U, + // 2 = kArm64. + 0, + // 3 = kThumb2. + 0, + // 4 = kX86. + (1 << kLoadStoreElimination) | + 0, + // 5 = kX86_64. + (1 << kLoadStoreElimination) | + 0, + // 6 = kMips. + (1 << kLoadStoreElimination) | + (1 << kLoadHoisting) | + (1 << kSuppressLoads) | + (1 << kNullCheckElimination) | + (1 << kPromoteRegs) | + (1 << kTrackLiveTemps) | + (1 << kSafeOptimizations) | + (1 << kBBOpt) | + (1 << kMatch) | + (1 << kPromoteCompilerTemps) | + 0, + // 7 = kMips64. + ~0U +}; +COMPILE_ASSERT(sizeof(kDisabledOptimizationsPerISA) == 8 * sizeof(uint32_t), kDisabledOpts_unexp); + +// Supported shorty types per instruction set. nullptr means that all are available. +// Z : boolean +// B : byte +// S : short +// C : char +// I : int +// J : long +// F : float +// D : double +// L : reference(object, array) +// V : void +static const char* kSupportedTypes[] = { + // 0 = kNone. + "", + // 1 = kArm, unused (will use kThumb2). + "", + // 2 = kArm64. + nullptr, + // 3 = kThumb2. + nullptr, + // 4 = kX86. + nullptr, + // 5 = kX86_64. + nullptr, + // 6 = kMips. + nullptr, + // 7 = kMips64. + "" +}; +COMPILE_ASSERT(sizeof(kSupportedTypes) == 8 * sizeof(char*), kSupportedTypes_unexp); + +static int kAllOpcodes[] = { + Instruction::NOP, + Instruction::MOVE, + Instruction::MOVE_FROM16, + Instruction::MOVE_16, + Instruction::MOVE_WIDE, + Instruction::MOVE_WIDE_FROM16, + Instruction::MOVE_WIDE_16, + Instruction::MOVE_OBJECT, + Instruction::MOVE_OBJECT_FROM16, + Instruction::MOVE_OBJECT_16, + Instruction::MOVE_RESULT, + Instruction::MOVE_RESULT_WIDE, + Instruction::MOVE_RESULT_OBJECT, + Instruction::MOVE_EXCEPTION, + Instruction::RETURN_VOID, + Instruction::RETURN, + Instruction::RETURN_WIDE, + Instruction::RETURN_OBJECT, + Instruction::CONST_4, + Instruction::CONST_16, + Instruction::CONST, + Instruction::CONST_HIGH16, + Instruction::CONST_WIDE_16, + Instruction::CONST_WIDE_32, + Instruction::CONST_WIDE, + Instruction::CONST_WIDE_HIGH16, + Instruction::CONST_STRING, + Instruction::CONST_STRING_JUMBO, + Instruction::CONST_CLASS, + Instruction::MONITOR_ENTER, + Instruction::MONITOR_EXIT, + Instruction::CHECK_CAST, + Instruction::INSTANCE_OF, + Instruction::ARRAY_LENGTH, + Instruction::NEW_INSTANCE, + Instruction::NEW_ARRAY, + Instruction::FILLED_NEW_ARRAY, + Instruction::FILLED_NEW_ARRAY_RANGE, + Instruction::FILL_ARRAY_DATA, + Instruction::THROW, + Instruction::GOTO, + Instruction::GOTO_16, + Instruction::GOTO_32, + Instruction::PACKED_SWITCH, + Instruction::SPARSE_SWITCH, + Instruction::CMPL_FLOAT, + Instruction::CMPG_FLOAT, + Instruction::CMPL_DOUBLE, + Instruction::CMPG_DOUBLE, + Instruction::CMP_LONG, + Instruction::IF_EQ, + Instruction::IF_NE, + Instruction::IF_LT, + Instruction::IF_GE, + Instruction::IF_GT, + Instruction::IF_LE, + Instruction::IF_EQZ, + Instruction::IF_NEZ, + Instruction::IF_LTZ, + Instruction::IF_GEZ, + Instruction::IF_GTZ, + Instruction::IF_LEZ, + Instruction::UNUSED_3E, + Instruction::UNUSED_3F, + Instruction::UNUSED_40, + Instruction::UNUSED_41, + Instruction::UNUSED_42, + Instruction::UNUSED_43, + Instruction::AGET, + Instruction::AGET_WIDE, + Instruction::AGET_OBJECT, + Instruction::AGET_BOOLEAN, + Instruction::AGET_BYTE, + Instruction::AGET_CHAR, + Instruction::AGET_SHORT, + Instruction::APUT, + Instruction::APUT_WIDE, + Instruction::APUT_OBJECT, + Instruction::APUT_BOOLEAN, + Instruction::APUT_BYTE, + Instruction::APUT_CHAR, + Instruction::APUT_SHORT, + Instruction::IGET, + Instruction::IGET_WIDE, + Instruction::IGET_OBJECT, + Instruction::IGET_BOOLEAN, + Instruction::IGET_BYTE, + Instruction::IGET_CHAR, + Instruction::IGET_SHORT, + Instruction::IPUT, + Instruction::IPUT_WIDE, + Instruction::IPUT_OBJECT, + Instruction::IPUT_BOOLEAN, + Instruction::IPUT_BYTE, + Instruction::IPUT_CHAR, + Instruction::IPUT_SHORT, + Instruction::SGET, + Instruction::SGET_WIDE, + Instruction::SGET_OBJECT, + Instruction::SGET_BOOLEAN, + Instruction::SGET_BYTE, + Instruction::SGET_CHAR, + Instruction::SGET_SHORT, + Instruction::SPUT, + Instruction::SPUT_WIDE, + Instruction::SPUT_OBJECT, + Instruction::SPUT_BOOLEAN, + Instruction::SPUT_BYTE, + Instruction::SPUT_CHAR, + Instruction::SPUT_SHORT, + Instruction::INVOKE_VIRTUAL, + Instruction::INVOKE_SUPER, + Instruction::INVOKE_DIRECT, + Instruction::INVOKE_STATIC, + Instruction::INVOKE_INTERFACE, + Instruction::RETURN_VOID_BARRIER, + Instruction::INVOKE_VIRTUAL_RANGE, + Instruction::INVOKE_SUPER_RANGE, + Instruction::INVOKE_DIRECT_RANGE, + Instruction::INVOKE_STATIC_RANGE, + Instruction::INVOKE_INTERFACE_RANGE, + Instruction::UNUSED_79, + Instruction::UNUSED_7A, + Instruction::NEG_INT, + Instruction::NOT_INT, + Instruction::NEG_LONG, + Instruction::NOT_LONG, + Instruction::NEG_FLOAT, + Instruction::NEG_DOUBLE, + Instruction::INT_TO_LONG, + Instruction::INT_TO_FLOAT, + Instruction::INT_TO_DOUBLE, + Instruction::LONG_TO_INT, + Instruction::LONG_TO_FLOAT, + Instruction::LONG_TO_DOUBLE, + Instruction::FLOAT_TO_INT, + Instruction::FLOAT_TO_LONG, + Instruction::FLOAT_TO_DOUBLE, + Instruction::DOUBLE_TO_INT, + Instruction::DOUBLE_TO_LONG, + Instruction::DOUBLE_TO_FLOAT, + Instruction::INT_TO_BYTE, + Instruction::INT_TO_CHAR, + Instruction::INT_TO_SHORT, + Instruction::ADD_INT, + Instruction::SUB_INT, + Instruction::MUL_INT, + Instruction::DIV_INT, + Instruction::REM_INT, + Instruction::AND_INT, + Instruction::OR_INT, + Instruction::XOR_INT, + Instruction::SHL_INT, + Instruction::SHR_INT, + Instruction::USHR_INT, + Instruction::ADD_LONG, + Instruction::SUB_LONG, + Instruction::MUL_LONG, + Instruction::DIV_LONG, + Instruction::REM_LONG, + Instruction::AND_LONG, + Instruction::OR_LONG, + Instruction::XOR_LONG, + Instruction::SHL_LONG, + Instruction::SHR_LONG, + Instruction::USHR_LONG, + Instruction::ADD_FLOAT, + Instruction::SUB_FLOAT, + Instruction::MUL_FLOAT, + Instruction::DIV_FLOAT, + Instruction::REM_FLOAT, + Instruction::ADD_DOUBLE, + Instruction::SUB_DOUBLE, + Instruction::MUL_DOUBLE, + Instruction::DIV_DOUBLE, + Instruction::REM_DOUBLE, + Instruction::ADD_INT_2ADDR, + Instruction::SUB_INT_2ADDR, + Instruction::MUL_INT_2ADDR, + Instruction::DIV_INT_2ADDR, + Instruction::REM_INT_2ADDR, + Instruction::AND_INT_2ADDR, + Instruction::OR_INT_2ADDR, + Instruction::XOR_INT_2ADDR, + Instruction::SHL_INT_2ADDR, + Instruction::SHR_INT_2ADDR, + Instruction::USHR_INT_2ADDR, + Instruction::ADD_LONG_2ADDR, + Instruction::SUB_LONG_2ADDR, + Instruction::MUL_LONG_2ADDR, + Instruction::DIV_LONG_2ADDR, + Instruction::REM_LONG_2ADDR, + Instruction::AND_LONG_2ADDR, + Instruction::OR_LONG_2ADDR, + Instruction::XOR_LONG_2ADDR, + Instruction::SHL_LONG_2ADDR, + Instruction::SHR_LONG_2ADDR, + Instruction::USHR_LONG_2ADDR, + Instruction::ADD_FLOAT_2ADDR, + Instruction::SUB_FLOAT_2ADDR, + Instruction::MUL_FLOAT_2ADDR, + Instruction::DIV_FLOAT_2ADDR, + Instruction::REM_FLOAT_2ADDR, + Instruction::ADD_DOUBLE_2ADDR, + Instruction::SUB_DOUBLE_2ADDR, + Instruction::MUL_DOUBLE_2ADDR, + Instruction::DIV_DOUBLE_2ADDR, + Instruction::REM_DOUBLE_2ADDR, + Instruction::ADD_INT_LIT16, + Instruction::RSUB_INT, + Instruction::MUL_INT_LIT16, + Instruction::DIV_INT_LIT16, + Instruction::REM_INT_LIT16, + Instruction::AND_INT_LIT16, + Instruction::OR_INT_LIT16, + Instruction::XOR_INT_LIT16, + Instruction::ADD_INT_LIT8, + Instruction::RSUB_INT_LIT8, + Instruction::MUL_INT_LIT8, + Instruction::DIV_INT_LIT8, + Instruction::REM_INT_LIT8, + Instruction::AND_INT_LIT8, + Instruction::OR_INT_LIT8, + Instruction::XOR_INT_LIT8, + Instruction::SHL_INT_LIT8, + Instruction::SHR_INT_LIT8, + Instruction::USHR_INT_LIT8, + Instruction::IGET_QUICK, + Instruction::IGET_WIDE_QUICK, + Instruction::IGET_OBJECT_QUICK, + Instruction::IPUT_QUICK, + Instruction::IPUT_WIDE_QUICK, + Instruction::IPUT_OBJECT_QUICK, + Instruction::INVOKE_VIRTUAL_QUICK, + Instruction::INVOKE_VIRTUAL_RANGE_QUICK, + Instruction::UNUSED_EB, + Instruction::UNUSED_EC, + Instruction::UNUSED_ED, + Instruction::UNUSED_EE, + Instruction::UNUSED_EF, + Instruction::UNUSED_F0, + Instruction::UNUSED_F1, + Instruction::UNUSED_F2, + Instruction::UNUSED_F3, + Instruction::UNUSED_F4, + Instruction::UNUSED_F5, + Instruction::UNUSED_F6, + Instruction::UNUSED_F7, + Instruction::UNUSED_F8, + Instruction::UNUSED_F9, + Instruction::UNUSED_FA, + Instruction::UNUSED_FB, + Instruction::UNUSED_FC, + Instruction::UNUSED_FD, + Instruction::UNUSED_FE, + Instruction::UNUSED_FF, + // ----- ExtendedMIROpcode ----- + kMirOpPhi, + kMirOpCopy, + kMirOpFusedCmplFloat, + kMirOpFusedCmpgFloat, + kMirOpFusedCmplDouble, + kMirOpFusedCmpgDouble, + kMirOpFusedCmpLong, + kMirOpNop, + kMirOpNullCheck, + kMirOpRangeCheck, + kMirOpDivZeroCheck, + kMirOpCheck, + kMirOpCheckPart2, + kMirOpSelect, +}; + +// Unsupported opcodes. nullptr can be used when everything is supported. Size of the lists is +// recorded below. +static const int* kUnsupportedOpcodes[] = { + // 0 = kNone. + kAllOpcodes, + // 1 = kArm, unused (will use kThumb2). + kAllOpcodes, + // 2 = kArm64. + nullptr, + // 3 = kThumb2. + nullptr, + // 4 = kX86. + nullptr, + // 5 = kX86_64. + nullptr, + // 6 = kMips. + nullptr, + // 7 = kMips64. + kAllOpcodes +}; +COMPILE_ASSERT(sizeof(kUnsupportedOpcodes) == 8 * sizeof(int*), kUnsupportedOpcodes_unexp); + +// Size of the arrays stored above. +static const size_t kUnsupportedOpcodesSize[] = { + // 0 = kNone. + arraysize(kAllOpcodes), + // 1 = kArm, unused (will use kThumb2). + arraysize(kAllOpcodes), + // 2 = kArm64. + 0, + // 3 = kThumb2. + 0, + // 4 = kX86. + 0, + // 5 = kX86_64. + 0, + // 6 = kMips. + 0, + // 7 = kMips64. + arraysize(kAllOpcodes), +}; +COMPILE_ASSERT(sizeof(kUnsupportedOpcodesSize) == 8 * sizeof(size_t), + kUnsupportedOpcodesSize_unexp); + +// The maximum amount of Dalvik register in a method for which we will start compiling. Tries to +// avoid an abort when we need to manage more SSA registers than we can. +static constexpr size_t kMaxAllowedDalvikRegisters = INT16_MAX / 2; + +CompilationUnit::CompilationUnit(ArenaPool* pool) + : compiler_driver(nullptr), + class_linker(nullptr), + dex_file(nullptr), + class_loader(nullptr), + class_def_idx(0), + method_idx(0), + code_item(nullptr), + access_flags(0), + invoke_type(kDirect), + shorty(nullptr), + disable_opt(0), + enable_debug(0), + verbose(false), + compiler(nullptr), + instruction_set(kNone), + target64(false), + num_dalvik_registers(0), + insns(nullptr), + num_ins(0), + num_outs(0), + num_regs(0), + compiler_flip_match(false), + arena(pool), + arena_stack(pool), + mir_graph(nullptr), + cg(nullptr), + timings("QuickCompiler", true, false), + print_pass(false) { +} + +CompilationUnit::~CompilationUnit() { +} + +void CompilationUnit::StartTimingSplit(const char* label) { + if (compiler_driver->GetDumpPasses()) { + timings.StartTiming(label); + } +} + +void CompilationUnit::NewTimingSplit(const char* label) { + if (compiler_driver->GetDumpPasses()) { + timings.EndTiming(); + timings.StartTiming(label); + } +} + +void CompilationUnit::EndTiming() { + if (compiler_driver->GetDumpPasses()) { + timings.EndTiming(); + if (enable_debug & (1 << kDebugTimings)) { + LOG(INFO) << "TIMINGS " << PrettyMethod(method_idx, *dex_file); + LOG(INFO) << Dumpable(timings); + } + } +} + +static bool CanCompileShorty(const char* shorty, InstructionSet instruction_set) { + const char* supported_types = kSupportedTypes[instruction_set]; + if (supported_types == nullptr) { + // Everything available. + return true; + } + + uint32_t shorty_size = strlen(shorty); + CHECK_GE(shorty_size, 1u); + + for (uint32_t i = 0; i < shorty_size; i++) { + if (strchr(supported_types, shorty[i]) == nullptr) { + return false; + } + } + return true; +}; + +// Skip the method that we do not support currently. +static bool CanCompileMethod(uint32_t method_idx, const DexFile& dex_file, + CompilationUnit& cu) { + // This is a limitation in mir_graph. See MirGraph::SetNumSSARegs. + if (cu.num_dalvik_registers > kMaxAllowedDalvikRegisters) { + VLOG(compiler) << "Too many dalvik registers : " << cu.num_dalvik_registers; + return false; + } + + // Check whether we do have limitations at all. + if (kSupportedTypes[cu.instruction_set] == nullptr && + kUnsupportedOpcodesSize[cu.instruction_set] == 0U) { + return true; + } + + // Check if we can compile the prototype. + const char* shorty = dex_file.GetMethodShorty(dex_file.GetMethodId(method_idx)); + if (!CanCompileShorty(shorty, cu.instruction_set)) { + VLOG(compiler) << "Unsupported shorty : " << shorty; + return false; + } + + const int *unsupport_list = kUnsupportedOpcodes[cu.instruction_set]; + int unsupport_list_size = kUnsupportedOpcodesSize[cu.instruction_set]; + + for (unsigned int idx = 0; idx < cu.mir_graph->GetNumBlocks(); idx++) { + BasicBlock* bb = cu.mir_graph->GetBasicBlock(idx); + if (bb == NULL) continue; + if (bb->block_type == kDead) continue; + for (MIR* mir = bb->first_mir_insn; mir != nullptr; mir = mir->next) { + int opcode = mir->dalvikInsn.opcode; + // Check if we support the byte code. + if (std::find(unsupport_list, unsupport_list + unsupport_list_size, + opcode) != unsupport_list + unsupport_list_size) { + if (!MIR::DecodedInstruction::IsPseudoMirOp(opcode)) { + VLOG(compiler) << "Unsupported dalvik byte code : " + << mir->dalvikInsn.opcode; + } else { + VLOG(compiler) << "Unsupported extended MIR opcode : " + << MIRGraph::extended_mir_op_names_[opcode - kMirOpFirst]; + } + return false; + } + // Check if it invokes a prototype that we cannot support. + if (Instruction::INVOKE_VIRTUAL == opcode || + Instruction::INVOKE_SUPER == opcode || + Instruction::INVOKE_DIRECT == opcode || + Instruction::INVOKE_STATIC == opcode || + Instruction::INVOKE_INTERFACE == opcode) { + uint32_t invoke_method_idx = mir->dalvikInsn.vB; + const char* invoke_method_shorty = dex_file.GetMethodShorty( + dex_file.GetMethodId(invoke_method_idx)); + if (!CanCompileShorty(invoke_method_shorty, cu.instruction_set)) { + VLOG(compiler) << "Unsupported to invoke '" + << PrettyMethod(invoke_method_idx, dex_file) + << "' with shorty : " << invoke_method_shorty; + return false; + } + } + } + } + return true; +} + +static CompiledMethod* CompileMethod(CompilerDriver& driver, + Compiler* compiler, const DexFile::CodeItem* code_item, uint32_t access_flags, InvokeType invoke_type, uint16_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 -) { + jobject class_loader, const DexFile& dex_file, + void* llvm_compilation_unit) { VLOG(compiler) << "Compiling " << PrettyMethod(method_idx, dex_file) << "..."; + /* + * Skip compilation for pathologically large methods - either by instruction count or num vregs. + * Dalvik uses 16-bit uints for instruction and register counts. We'll limit to a quarter + * of that, which also guarantees we cannot overflow our 16-bit internal SSA name space. + */ + if (code_item->insns_size_in_code_units_ >= UINT16_MAX / 4) { + LOG(INFO) << "Method exceeds compiler instruction limit: " + << code_item->insns_size_in_code_units_ + << " in " << PrettyMethod(method_idx, dex_file); + return NULL; + } + if (code_item->registers_size_ >= UINT16_MAX / 4) { + LOG(INFO) << "Method exceeds compiler virtual register limit: " + << code_item->registers_size_ << " in " << PrettyMethod(method_idx, dex_file); + return NULL; + } + + if (!driver.GetCompilerOptions().IsCompilationEnabled()) { + return nullptr; + } ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); - CompilationUnit cu(&compiler.GetArenaPool()); + CompilationUnit cu(driver.GetArenaPool()); - cu.compiler_driver = &compiler; + cu.compiler_driver = &driver; 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)); - + cu.instruction_set = driver.GetInstructionSet(); + if (cu.instruction_set == kArm) { + cu.instruction_set = kThumb2; + } + cu.target64 = Is64BitInstructionSet(cu.instruction_set); + cu.compiler = compiler; + // TODO: Mips64 is not yet implemented. + CHECK((cu.instruction_set == kThumb2) || + (cu.instruction_set == kArm64) || + (cu.instruction_set == kX86) || + (cu.instruction_set == kX86_64) || + (cu.instruction_set == kMips)); /* Adjust this value accordingly once inlining is performed */ cu.num_dalvik_registers = code_item->registers_size_; @@ -136,8 +668,7 @@ static CompiledMethod* CompileMethod(CompilerDriver& compiler, 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)); + (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; @@ -145,33 +676,41 @@ static CompiledMethod* CompileMethod(CompilerDriver& compiler, (cu.enable_debug & (1 << kDebugVerbose)); } + if (gVerboseMethods.size() != 0) { + cu.verbose = false; + for (size_t i = 0; i < gVerboseMethods.size(); ++i) { + if (PrettyMethod(method_idx, dex_file).find(gVerboseMethods[i]) + != std::string::npos) { + cu.verbose = true; + break; + } + } + } + + if (cu.verbose) { + cu.enable_debug |= (1 << kDebugCodegenDump); + } + /* * 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); - } + compiler->InitCompilationUnit(cu); - 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)); - } + // Disable optimizations according to instruction set. + cu.disable_opt |= kDisabledOptimizationsPerISA[cu.instruction_set]; + cu.StartTimingSplit("BuildMIRGraph"); cu.mir_graph.reset(new MIRGraph(&cu, &cu.arena)); + /* + * After creation of the MIR graph, also create the code generator. + * The reason we do this is that optimizations on the MIR graph may need to get information + * that is only available if a CG exists. + */ + cu.cg.reset(compiler->GetCodeGenerator(&cu, llvm_compilation_unit)); + /* Gathering opcode stats? */ if (kCompilerDebugFlags & (1 << kDebugCountOpcodes)) { cu.mir_graph->EnableOpcodeCounting(); @@ -181,32 +720,30 @@ static CompiledMethod* CompileMethod(CompilerDriver& compiler, cu.mir_graph->InlineMethod(code_item, access_flags, invoke_type, class_def_idx, method_idx, class_loader, dex_file); -#if !defined(ART_USE_PORTABLE_COMPILER) - if (cu.mir_graph->SkipCompilation(Runtime::Current()->GetCompilerFilter())) { - return NULL; + if (!CanCompileMethod(method_idx, dex_file, cu)) { + VLOG(compiler) << cu.instruction_set << ": Cannot compile method : " + << PrettyMethod(method_idx, dex_file); + return nullptr; } -#endif - - /* 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(); + cu.NewTimingSplit("MIROpt:CheckFilters"); + std::string skip_message; + if (cu.mir_graph->SkipCompilation(&skip_message)) { + VLOG(compiler) << cu.instruction_set << ": Skipping method : " + << PrettyMethod(method_idx, dex_file) << " Reason = " << skip_message; + return nullptr; + } - /* Combine basic blocks where possible */ - cu.mir_graph->BasicBlockCombine(); + /* Create the pass driver and launch it */ + PassDriverMEOpts pass_driver(&cu); + pass_driver.Launch(); - /* Do some basic block optimizations */ - cu.mir_graph->BasicBlockOptimization(); + /* For non-leaf methods check if we should skip compilation when the profiler is enabled. */ + if (cu.compiler_driver->ProfilePresent() + && !cu.mir_graph->MethodIsLeaf() + && cu.mir_graph->SkipCompilationByName(PrettyMethod(method_idx, dex_file))) { + return nullptr; + } if (cu.enable_debug & (1 << kDebugDumpCheckStats)) { cu.mir_graph->DumpCheckStats(); @@ -216,46 +753,41 @@ static CompiledMethod* CompileMethod(CompilerDriver& compiler, cu.mir_graph->ShowOpcodeStats(); } - /* Set up regLocation[] array to describe values - one for each ssa_name. */ - cu.mir_graph->BuildRegLocations(); + /* Reassociate sreg names with original Dalvik vreg names. */ + cu.mir_graph->RemapRegLocations(); + + /* Free Arenas from the cu.arena_stack for reuse by the cu.arena in the codegen. */ + if (cu.enable_debug & (1 << kDebugShowMemoryUsage)) { + if (cu.arena_stack.PeakBytesAllocated() > 1 * 1024 * 1024) { + MemStats stack_stats(cu.arena_stack.GetPeakStats()); + LOG(INFO) << PrettyMethod(method_idx, dex_file) << " " << Dumpable(stack_stats); + } + } + cu.arena_stack.Reset(); CompiledMethod* result = NULL; -#if defined(ART_USE_PORTABLE_COMPILER) - if (compiler_backend == kPortable) { - cu.cg.reset(PortableCodeGenerator(&cu, cu.mir_graph.get(), &cu.arena, llvm_compilation_unit)); - } else { -#endif - switch (compiler.GetInstructionSet()) { - case kThumb2: - cu.cg.reset(ArmCodeGenerator(&cu, cu.mir_graph.get(), &cu.arena)); - break; - case kMips: - cu.cg.reset(MipsCodeGenerator(&cu, cu.mir_graph.get(), &cu.arena)); - break; - case kX86: - cu.cg.reset(X86CodeGenerator(&cu, cu.mir_graph.get(), &cu.arena)); - break; - default: - LOG(FATAL) << "Unexpected instruction set: " << compiler.GetInstructionSet(); - } -#if defined(ART_USE_PORTABLE_COMPILER) + if (cu.mir_graph->PuntToInterpreter()) { + VLOG(compiler) << cu.instruction_set << ": Punted method to interpreter: " + << PrettyMethod(method_idx, dex_file); + return nullptr; } -#endif cu.cg->Materialize(); + cu.NewTimingSplit("Dedupe"); /* deduping takes up the vast majority of time in GetCompiledMethod(). */ result = cu.cg->GetCompiledMethod(); + cu.NewTimingSplit("Cleanup"); if (result) { - VLOG(compiler) << "Compiled " << PrettyMethod(method_idx, dex_file); + VLOG(compiler) << cu.instruction_set << ": Compiled " << PrettyMethod(method_idx, dex_file); } else { - VLOG(compiler) << "Deferred " << PrettyMethod(method_idx, dex_file); + VLOG(compiler) << cu.instruction_set << ": Deferred " << PrettyMethod(method_idx, dex_file); } if (cu.enable_debug & (1 << kDebugShowMemoryUsage)) { - if (cu.arena.BytesAllocated() > (5 * 1024 *1024)) { - MemStats mem_stats(cu.arena); + if (cu.arena.BytesAllocated() > (1 * 1024 *1024)) { + MemStats mem_stats(cu.arena.GetMemStats()); LOG(INFO) << PrettyMethod(method_idx, dex_file) << " " << Dumpable(mem_stats); } } @@ -265,11 +797,13 @@ static CompiledMethod* CompileMethod(CompilerDriver& compiler, << " " << PrettyMethod(method_idx, dex_file); } + cu.EndTiming(); + driver.GetTimingsLogger()->AddLogger(cu.timings); return result; } -CompiledMethod* CompileOneMethod(CompilerDriver& compiler, - const CompilerBackend backend, +CompiledMethod* CompileOneMethod(CompilerDriver& driver, + Compiler* compiler, const DexFile::CodeItem* code_item, uint32_t access_flags, InvokeType invoke_type, @@ -277,26 +811,23 @@ CompiledMethod* CompileOneMethod(CompilerDriver& compiler, 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 - ); // NOLINT(whitespace/parens) + void* compilation_unit) { + return CompileMethod(driver, compiler, code_item, access_flags, invoke_type, class_def_idx, + method_idx, class_loader, dex_file, compilation_unit); } } // namespace art extern "C" art::CompiledMethod* - ArtQuickCompileMethod(art::CompilerDriver& compiler, + ArtQuickCompileMethod(art::CompilerDriver& driver, const art::DexFile::CodeItem* code_item, uint32_t access_flags, art::InvokeType invoke_type, uint16_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, + // TODO: check method fingerprint here to determine appropriate backend type. Until then, use + // build default. + art::Compiler* compiler = driver.GetCompiler(); + return art::CompileOneMethod(driver, compiler, code_item, access_flags, invoke_type, class_def_idx, method_idx, class_loader, dex_file, NULL /* use thread llvm_info */); } diff --git a/compiler/dex/frontend.h b/compiler/dex/frontend.h index 6c33d109e31..f4cbdfbe6c7 100644 --- a/compiler/dex/frontend.h +++ b/compiler/dex/frontend.h @@ -18,12 +18,7 @@ #define ART_COMPILER_DEX_FRONTEND_H_ #include "dex_file.h" -#include "dex_instruction.h" - - - - - +#include "invoke_type.h" namespace llvm { class Module; @@ -49,6 +44,8 @@ enum opt_control_vector { kLoadHoisting, kSuppressLoads, kNullCheckElimination, + kClassInitCheckElimination, + kGlobalValueNumbering, kPromoteRegs, kTrackLiveTemps, kSafeOptimizations, @@ -56,6 +53,8 @@ enum opt_control_vector { kMatch, kPromoteCompilerTemps, kBranchFusing, + kSuppressExceptionEdges, + kSuppressMethodInlining, }; // Force code generation paths for testing. @@ -78,6 +77,8 @@ enum debugControlVector { kDebugVerifyBitcode, kDebugShowSummaryMemoryUsage, kDebugShowFilterStats, + kDebugTimings, + kDebugCodegenDump }; class LLVMInfo { @@ -102,14 +103,14 @@ class LLVMInfo { } private: - UniquePtr< ::llvm::LLVMContext> llvm_context_; + std::unique_ptr< ::llvm::LLVMContext> llvm_context_; ::llvm::Module* llvm_module_; // Managed by context_. - UniquePtr intrinsic_helper_; - UniquePtr ir_builder_; + std::unique_ptr intrinsic_helper_; + std::unique_ptr ir_builder_; }; -struct CompilationUnit; -struct BasicBlock; +class CompiledMethod; +class CompilerDriver; } // namespace art diff --git a/compiler/dex/global_value_numbering.cc b/compiler/dex/global_value_numbering.cc new file mode 100644 index 00000000000..d7ef6f09849 --- /dev/null +++ b/compiler/dex/global_value_numbering.cc @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "global_value_numbering.h" + +#include "local_value_numbering.h" + +namespace art { + +GlobalValueNumbering::GlobalValueNumbering(CompilationUnit* cu, ScopedArenaAllocator* allocator) + : cu_(cu), + mir_graph_(cu->mir_graph.get()), + allocator_(allocator), + bbs_processed_(0u), + max_bbs_to_process_(kMaxBbsToProcessMultiplyFactor * mir_graph_->GetNumReachableBlocks()), + last_value_(0u), + modifications_allowed_(false), + global_value_map_(std::less(), allocator->Adapter()), + field_index_map_(FieldReferenceComparator(), allocator->Adapter()), + field_index_reverse_map_(allocator->Adapter()), + array_location_map_(ArrayLocationComparator(), allocator->Adapter()), + array_location_reverse_map_(allocator->Adapter()), + ref_set_map_(std::less(), allocator->Adapter()), + lvns_(mir_graph_->GetNumBlocks(), nullptr, allocator->Adapter()), + work_lvn_(nullptr), + merge_lvns_(allocator->Adapter()) { +} + +GlobalValueNumbering::~GlobalValueNumbering() { + STLDeleteElements(&lvns_); +} + +LocalValueNumbering* GlobalValueNumbering::PrepareBasicBlock(BasicBlock* bb, + ScopedArenaAllocator* allocator) { + if (UNLIKELY(!Good())) { + return nullptr; + } + if (UNLIKELY(bb->data_flow_info == nullptr)) { + return nullptr; + } + if (UNLIKELY(bb->block_type == kExitBlock)) { + DCHECK(bb->first_mir_insn == nullptr); + return nullptr; + } + if (UNLIKELY(bbs_processed_ == max_bbs_to_process_)) { + last_value_ = kNoValue; // Make bad. + return nullptr; + } + if (allocator == nullptr) { + allocator = allocator_; + } + DCHECK(work_lvn_.get() == nullptr); + work_lvn_.reset(new (allocator) LocalValueNumbering(this, bb->id, allocator)); + if (bb->block_type == kEntryBlock) { + 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; + uint16_t value_name = work_lvn_->GetSRegValueName(this_reg); + work_lvn_->SetValueNameNullChecked(value_name); + } + } else { + // To avoid repeated allocation on the ArenaStack, reuse a single vector kept as a member. + DCHECK(merge_lvns_.empty()); + // If we're running the full GVN, the RepeatingTopologicalSortIterator keeps the loop + // head stack in the MIRGraph up to date and for a loop head we need to check whether + // we're making the initial computation and need to merge only preceding blocks in the + // topological order, or we're recalculating a loop head and need to merge all incoming + // LVNs. When we're not at a loop head (including having an empty loop head stack) all + // predecessors should be preceding blocks and we shall merge all of them anyway. + // + // If we're running the modification phase of the full GVN, the loop head stack will be + // empty and we need to merge all incoming LVNs. If we're running just a simple LVN, + // the loop head stack will also be empty and there will be nothing to merge anyway. + bool use_all_predecessors = true; + uint16_t loop_head_idx = 0u; // Used only if !use_all_predecessors. + if (mir_graph_->GetTopologicalSortOrderLoopHeadStack()->Size() != 0) { + // Full GVN inside a loop, see if we're at the loop head for the first time. + auto top = mir_graph_->GetTopologicalSortOrderLoopHeadStack()->Peek(); + loop_head_idx = top.first; + bool recalculating = top.second; + use_all_predecessors = recalculating || + loop_head_idx != mir_graph_->GetTopologicalSortOrderIndexes()->Get(bb->id); + } + GrowableArray::Iterator iter(bb->predecessors); + for (BasicBlock* pred_bb = mir_graph_->GetBasicBlock(iter.Next()); + pred_bb != nullptr; pred_bb = mir_graph_->GetBasicBlock(iter.Next())) { + if (lvns_[pred_bb->id] != nullptr && + (use_all_predecessors || + mir_graph_->GetTopologicalSortOrderIndexes()->Get(pred_bb->id) < loop_head_idx)) { + merge_lvns_.push_back(lvns_[pred_bb->id]); + } + } + // Determine merge type. + LocalValueNumbering::MergeType merge_type = LocalValueNumbering::kNormalMerge; + if (bb->catch_entry) { + merge_type = LocalValueNumbering::kCatchMerge; + } else if (bb->last_mir_insn != nullptr && + (bb->last_mir_insn->dalvikInsn.opcode == Instruction::RETURN_VOID || + bb->last_mir_insn->dalvikInsn.opcode == Instruction::RETURN || + bb->last_mir_insn->dalvikInsn.opcode == Instruction::RETURN_OBJECT || + bb->last_mir_insn->dalvikInsn.opcode == Instruction::RETURN_WIDE) && + (bb->first_mir_insn == bb->last_mir_insn || + (static_cast(bb->first_mir_insn->dalvikInsn.opcode) == kMirOpPhi && + (bb->first_mir_insn->next == bb->last_mir_insn || + (static_cast(bb->first_mir_insn->next->dalvikInsn.opcode) == kMirOpPhi && + bb->first_mir_insn->next->next == bb->last_mir_insn))))) { + merge_type = LocalValueNumbering::kReturnMerge; + } + // At least one predecessor must have been processed before this bb. + CHECK(!merge_lvns_.empty()); + if (merge_lvns_.size() == 1u) { + work_lvn_->MergeOne(*merge_lvns_[0], merge_type); + BasicBlock* pred_bb = mir_graph_->GetBasicBlock(merge_lvns_[0]->Id()); + if (HasNullCheckLastInsn(pred_bb, bb->id)) { + int s_reg = pred_bb->last_mir_insn->ssa_rep->uses[0]; + uint16_t value_name = merge_lvns_[0]->GetSRegValueName(s_reg); + work_lvn_->SetValueNameNullChecked(value_name); + } + } else { + work_lvn_->Merge(merge_type); + } + } + return work_lvn_.get(); +} + +bool GlobalValueNumbering::FinishBasicBlock(BasicBlock* bb) { + DCHECK(work_lvn_ != nullptr); + DCHECK_EQ(bb->id, work_lvn_->Id()); + ++bbs_processed_; + merge_lvns_.clear(); + + bool change = (lvns_[bb->id] == nullptr) || !lvns_[bb->id]->Equals(*work_lvn_); + if (change) { + std::unique_ptr old_lvn(lvns_[bb->id]); + lvns_[bb->id] = work_lvn_.release(); + } else { + work_lvn_.reset(); + } + return change; +} + +uint16_t GlobalValueNumbering::GetFieldId(const MirFieldInfo& field_info, uint16_t type) { + FieldReference key = { field_info.DeclaringDexFile(), field_info.DeclaringFieldIndex(), type }; + auto lb = field_index_map_.lower_bound(key); + if (lb != field_index_map_.end() && !field_index_map_.key_comp()(key, lb->first)) { + return lb->second; + } + DCHECK_LT(field_index_map_.size(), kNoValue); + uint16_t id = field_index_map_.size(); + auto it = field_index_map_.PutBefore(lb, key, id); + field_index_reverse_map_.push_back(&*it); + return id; +} + +uint16_t GlobalValueNumbering::GetArrayLocation(uint16_t base, uint16_t index) { + auto cmp = array_location_map_.key_comp(); + ArrayLocation key = { base, index }; + auto lb = array_location_map_.lower_bound(key); + if (lb != array_location_map_.end() && !cmp(key, lb->first)) { + return lb->second; + } + uint16_t location = static_cast(array_location_reverse_map_.size()); + DCHECK_EQ(location, array_location_reverse_map_.size()); // No overflow. + auto it = array_location_map_.PutBefore(lb, key, location); + array_location_reverse_map_.push_back(&*it); + return location; +} + +bool GlobalValueNumbering::HasNullCheckLastInsn(const BasicBlock* pred_bb, + BasicBlockId succ_id) { + if (pred_bb->block_type != kDalvikByteCode || pred_bb->last_mir_insn == nullptr) { + return false; + } + Instruction::Code last_opcode = pred_bb->last_mir_insn->dalvikInsn.opcode; + return ((last_opcode == Instruction::IF_EQZ && pred_bb->fall_through == succ_id) || + (last_opcode == Instruction::IF_NEZ && pred_bb->taken == succ_id)); +} + +bool GlobalValueNumbering::NullCheckedInAllPredecessors( + const ScopedArenaVector& merge_names) const { + // Implicit parameters: + // - *work_lvn: the LVN for which we're checking predecessors. + // - merge_lvns_: the predecessor LVNs. + DCHECK_EQ(merge_lvns_.size(), merge_names.size()); + for (size_t i = 0, size = merge_lvns_.size(); i != size; ++i) { + const LocalValueNumbering* pred_lvn = merge_lvns_[i]; + uint16_t value_name = merge_names[i]; + if (!pred_lvn->IsValueNullChecked(value_name)) { + // Check if the predecessor has an IF_EQZ/IF_NEZ as the last insn. + const BasicBlock* pred_bb = mir_graph_->GetBasicBlock(pred_lvn->Id()); + if (!HasNullCheckLastInsn(pred_bb, work_lvn_->Id())) { + return false; + } + // IF_EQZ/IF_NEZ checks some sreg, see if that sreg contains the value_name. + int s_reg = pred_bb->last_mir_insn->ssa_rep->uses[0]; + if (!pred_lvn->IsSregValue(s_reg, value_name)) { + return false; + } + } + } + return true; +} + +} // namespace art diff --git a/compiler/dex/global_value_numbering.h b/compiler/dex/global_value_numbering.h new file mode 100644 index 00000000000..c06ff6f1721 --- /dev/null +++ b/compiler/dex/global_value_numbering.h @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_DEX_GLOBAL_VALUE_NUMBERING_H_ +#define ART_COMPILER_DEX_GLOBAL_VALUE_NUMBERING_H_ + +#include "base/macros.h" +#include "compiler_internals.h" +#include "utils/scoped_arena_containers.h" + +namespace art { + +class LocalValueNumbering; +class MirFieldInfo; + +class GlobalValueNumbering { + public: + GlobalValueNumbering(CompilationUnit* cu, ScopedArenaAllocator* allocator); + ~GlobalValueNumbering(); + + // Prepare LVN for the basic block. + LocalValueNumbering* PrepareBasicBlock(BasicBlock* bb, + ScopedArenaAllocator* allocator = nullptr); + + // Finish processing the basic block. + bool FinishBasicBlock(BasicBlock* bb); + + // Checks that the value names didn't overflow. + bool Good() const { + return last_value_ < kNoValue; + } + + // Allow modifications. + void AllowModifications() { + DCHECK(Good()); + modifications_allowed_ = true; + } + + bool CanModify() const { + // TODO: DCHECK(Good()), see AllowModifications() and NewValueName(). + return modifications_allowed_ && Good(); + } + + // GlobalValueNumbering should be allocated on the ArenaStack (or the native stack). + static void* operator new(size_t size, ScopedArenaAllocator* allocator) { + return allocator->Alloc(sizeof(GlobalValueNumbering), kArenaAllocMisc); + } + + // Allow delete-expression to destroy a GlobalValueNumbering object without deallocation. + static void operator delete(void* ptr) { UNUSED(ptr); } + + private: + static constexpr uint16_t kNoValue = 0xffffu; + + // Allocate a new value name. + uint16_t NewValueName() { + // TODO: No new values should be needed once we allow modifications. + // DCHECK(!modifications_allowed_); + ++last_value_; + return last_value_; + } + + // Key is concatenation of opcode, operand1, operand2 and modifier, value is value name. + typedef ScopedArenaSafeMap ValueMap; + + 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)); + }; + + // Look up a value in the global value map, adding a new entry if there was none before. + 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 lb = global_value_map_.lower_bound(key); + if (lb != global_value_map_.end() && lb->first == key) { + res = lb->second; + } else { + res = NewValueName(); + global_value_map_.PutBefore(lb, key, res); + } + return res; + }; + + // Check if the exact value is stored in the global value map. + bool HasValue(uint16_t op, uint16_t operand1, uint16_t operand2, uint16_t modifier, + uint16_t value) const { + DCHECK(value != 0u || !Good()); + DCHECK_LE(value, last_value_); + // This is equivalent to value == LookupValue(op, operand1, operand2, modifier) + // except that it doesn't add an entry to the global value map if it's not there. + uint64_t key = BuildKey(op, operand1, operand2, modifier); + ValueMap::const_iterator it = global_value_map_.find(key); + return (it != global_value_map_.end() && it->second == value); + }; + + // FieldReference represents a unique resolved field. + struct FieldReference { + const DexFile* dex_file; + uint16_t field_idx; + uint16_t type; // See comments for LocalValueNumbering::kFieldTypeCount. + }; + + struct FieldReferenceComparator { + bool operator()(const FieldReference& lhs, const FieldReference& rhs) const { + if (lhs.field_idx != rhs.field_idx) { + return lhs.field_idx < rhs.field_idx; + } + // If the field_idx and dex_file match, the type must also match. + DCHECK(lhs.dex_file != rhs.dex_file || lhs.type == rhs.type); + return lhs.dex_file < rhs.dex_file; + } + }; + + // Maps field key to field id for resolved fields. + typedef ScopedArenaSafeMap FieldIndexMap; + + // Get a field id. + uint16_t GetFieldId(const MirFieldInfo& field_info, uint16_t type); + + // Get a field type based on field id. + uint16_t GetFieldType(uint16_t field_id) { + DCHECK_LT(field_id, field_index_reverse_map_.size()); + return field_index_reverse_map_[field_id]->first.type; + } + + struct ArrayLocation { + uint16_t base; + uint16_t index; + }; + + struct ArrayLocationComparator { + bool operator()(const ArrayLocation& lhs, const ArrayLocation& rhs) const { + if (lhs.base != rhs.base) { + return lhs.base < rhs.base; + } + return lhs.index < rhs.index; + } + }; + + typedef ScopedArenaSafeMap ArrayLocationMap; + + // Get an array location. + uint16_t GetArrayLocation(uint16_t base, uint16_t index); + + // Get the array base from an array location. + uint16_t GetArrayLocationBase(uint16_t location) const { + return array_location_reverse_map_[location]->first.base; + } + + // Get the array index from an array location. + uint16_t GetArrayLocationIndex(uint16_t location) const { + return array_location_reverse_map_[location]->first.index; + } + + // A set of value names. + typedef ScopedArenaSet ValueNameSet; + + // A map from a set of references to the set id. + typedef ScopedArenaSafeMap RefSetIdMap; + + uint16_t GetRefSetId(const ValueNameSet& ref_set) { + uint16_t res = kNoValue; + auto lb = ref_set_map_.lower_bound(ref_set); + if (lb != ref_set_map_.end() && !ref_set_map_.key_comp()(ref_set, lb->first)) { + res = lb->second; + } else { + res = NewValueName(); + ref_set_map_.PutBefore(lb, ref_set, res); + } + return res; + } + + const BasicBlock* GetBasicBlock(uint16_t bb_id) const { + return mir_graph_->GetBasicBlock(bb_id); + } + + static bool HasNullCheckLastInsn(const BasicBlock* pred_bb, BasicBlockId succ_id); + + bool NullCheckedInAllPredecessors(const ScopedArenaVector& merge_names) const; + + CompilationUnit* GetCompilationUnit() const { + return cu_; + } + + MIRGraph* GetMirGraph() const { + return mir_graph_; + } + + ScopedArenaAllocator* Allocator() const { + return allocator_; + } + + CompilationUnit* const cu_; + MIRGraph* mir_graph_; + ScopedArenaAllocator* const allocator_; + + // The number of BBs that we need to process grows exponentially with the number + // of nested loops. Don't allow excessive processing for too many nested loops or + // otherwise expensive methods. + static constexpr uint32_t kMaxBbsToProcessMultiplyFactor = 20u; + + uint32_t bbs_processed_; + uint32_t max_bbs_to_process_; + + // We have 32-bit last_value_ so that we can detect when we run out of value names, see Good(). + // We usually don't check Good() until the end of LVN unless we're about to modify code. + uint32_t last_value_; + + // Marks whether code modifications are allowed. The initial GVN is done without code + // modifications to settle the value names. Afterwards, we allow modifications and rerun + // LVN once for each BasicBlock. + bool modifications_allowed_; + + ValueMap global_value_map_; + FieldIndexMap field_index_map_; + ScopedArenaVector field_index_reverse_map_; + ArrayLocationMap array_location_map_; + ScopedArenaVector array_location_reverse_map_; + RefSetIdMap ref_set_map_; + + ScopedArenaVector lvns_; // Owning. + std::unique_ptr work_lvn_; + ScopedArenaVector merge_lvns_; // Not owning. + + friend class LocalValueNumbering; + + DISALLOW_COPY_AND_ASSIGN(GlobalValueNumbering); +}; + +} // namespace art + +#endif // ART_COMPILER_DEX_GLOBAL_VALUE_NUMBERING_H_ diff --git a/compiler/dex/global_value_numbering_test.cc b/compiler/dex/global_value_numbering_test.cc new file mode 100644 index 00000000000..e8501cd405e --- /dev/null +++ b/compiler/dex/global_value_numbering_test.cc @@ -0,0 +1,2131 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compiler_internals.h" +#include "dataflow_iterator.h" +#include "dataflow_iterator-inl.h" +#include "global_value_numbering.h" +#include "local_value_numbering.h" +#include "gtest/gtest.h" + +namespace art { + +class GlobalValueNumberingTest : public testing::Test { + protected: + struct IFieldDef { + uint16_t field_idx; + uintptr_t declaring_dex_file; + uint16_t declaring_field_idx; + bool is_volatile; + }; + + struct SFieldDef { + uint16_t field_idx; + uintptr_t declaring_dex_file; + uint16_t declaring_field_idx; + bool is_volatile; + }; + + struct BBDef { + static constexpr size_t kMaxSuccessors = 4; + static constexpr size_t kMaxPredecessors = 4; + + BBType type; + size_t num_successors; + BasicBlockId successors[kMaxPredecessors]; + size_t num_predecessors; + BasicBlockId predecessors[kMaxPredecessors]; + }; + + struct MIRDef { + static constexpr size_t kMaxSsaDefs = 2; + static constexpr size_t kMaxSsaUses = 4; + + BasicBlockId bbid; + Instruction::Code opcode; + int64_t value; + uint32_t field_info; + size_t num_uses; + int32_t uses[kMaxSsaUses]; + size_t num_defs; + int32_t defs[kMaxSsaDefs]; + }; + +#define DEF_SUCC0() \ + 0u, { } +#define DEF_SUCC1(s1) \ + 1u, { s1 } +#define DEF_SUCC2(s1, s2) \ + 2u, { s1, s2 } +#define DEF_SUCC3(s1, s2, s3) \ + 3u, { s1, s2, s3 } +#define DEF_SUCC4(s1, s2, s3, s4) \ + 4u, { s1, s2, s3, s4 } +#define DEF_PRED0() \ + 0u, { } +#define DEF_PRED1(p1) \ + 1u, { p1 } +#define DEF_PRED2(p1, p2) \ + 2u, { p1, p2 } +#define DEF_PRED3(p1, p2, p3) \ + 3u, { p1, p2, p3 } +#define DEF_PRED4(p1, p2, p3, p4) \ + 4u, { p1, p2, p3, p4 } +#define DEF_BB(type, succ, pred) \ + { type, succ, pred } + +#define DEF_CONST(bb, opcode, reg, value) \ + { bb, opcode, value, 0u, 0, { }, 1, { reg } } +#define DEF_CONST_WIDE(bb, opcode, reg, value) \ + { bb, opcode, value, 0u, 0, { }, 2, { reg, reg + 1 } } +#define DEF_CONST_STRING(bb, opcode, reg, index) \ + { bb, opcode, index, 0u, 0, { }, 1, { reg } } +#define DEF_IGET(bb, opcode, reg, obj, field_info) \ + { bb, opcode, 0u, field_info, 1, { obj }, 1, { reg } } +#define DEF_IGET_WIDE(bb, opcode, reg, obj, field_info) \ + { bb, opcode, 0u, field_info, 1, { obj }, 2, { reg, reg + 1 } } +#define DEF_IPUT(bb, opcode, reg, obj, field_info) \ + { bb, opcode, 0u, field_info, 2, { reg, obj }, 0, { } } +#define DEF_IPUT_WIDE(bb, opcode, reg, obj, field_info) \ + { bb, opcode, 0u, field_info, 3, { reg, reg + 1, obj }, 0, { } } +#define DEF_SGET(bb, opcode, reg, field_info) \ + { bb, opcode, 0u, field_info, 0, { }, 1, { reg } } +#define DEF_SGET_WIDE(bb, opcode, reg, field_info) \ + { bb, opcode, 0u, field_info, 0, { }, 2, { reg, reg + 1 } } +#define DEF_SPUT(bb, opcode, reg, field_info) \ + { bb, opcode, 0u, field_info, 1, { reg }, 0, { } } +#define DEF_SPUT_WIDE(bb, opcode, reg, field_info) \ + { bb, opcode, 0u, field_info, 2, { reg, reg + 1 }, 0, { } } +#define DEF_AGET(bb, opcode, reg, obj, idx) \ + { bb, opcode, 0u, 0u, 2, { obj, idx }, 1, { reg } } +#define DEF_AGET_WIDE(bb, opcode, reg, obj, idx) \ + { bb, opcode, 0u, 0u, 2, { obj, idx }, 2, { reg, reg + 1 } } +#define DEF_APUT(bb, opcode, reg, obj, idx) \ + { bb, opcode, 0u, 0u, 3, { reg, obj, idx }, 0, { } } +#define DEF_APUT_WIDE(bb, opcode, reg, obj, idx) \ + { bb, opcode, 0u, 0u, 4, { reg, reg + 1, obj, idx }, 0, { } } +#define DEF_INVOKE1(bb, opcode, reg) \ + { bb, opcode, 0u, 0u, 1, { reg }, 0, { } } +#define DEF_UNIQUE_REF(bb, opcode, reg) \ + { bb, opcode, 0u, 0u, 0, { }, 1, { reg } } // CONST_CLASS, CONST_STRING, NEW_ARRAY, ... +#define DEF_IFZ(bb, opcode, reg) \ + { bb, opcode, 0u, 0u, 1, { reg }, 0, { } } +#define DEF_MOVE(bb, opcode, reg, src) \ + { bb, opcode, 0u, 0u, 1, { src }, 1, { reg } } +#define DEF_PHI2(bb, reg, src1, src2) \ + { bb, static_cast(kMirOpPhi), 0, 0u, 2u, { src1, src2 }, 1, { reg } } + + void DoPrepareIFields(const IFieldDef* defs, size_t count) { + cu_.mir_graph->ifield_lowering_infos_.Reset(); + cu_.mir_graph->ifield_lowering_infos_.Resize(count); + for (size_t i = 0u; i != count; ++i) { + const IFieldDef* def = &defs[i]; + MirIFieldLoweringInfo field_info(def->field_idx); + if (def->declaring_dex_file != 0u) { + field_info.declaring_dex_file_ = reinterpret_cast(def->declaring_dex_file); + field_info.declaring_field_idx_ = def->declaring_field_idx; + field_info.flags_ = 0u | // Without kFlagIsStatic. + (def->is_volatile ? MirIFieldLoweringInfo::kFlagIsVolatile : 0u); + } + cu_.mir_graph->ifield_lowering_infos_.Insert(field_info); + } + } + + template + void PrepareIFields(const IFieldDef (&defs)[count]) { + DoPrepareIFields(defs, count); + } + + void DoPrepareSFields(const SFieldDef* defs, size_t count) { + cu_.mir_graph->sfield_lowering_infos_.Reset(); + cu_.mir_graph->sfield_lowering_infos_.Resize(count); + for (size_t i = 0u; i != count; ++i) { + const SFieldDef* def = &defs[i]; + MirSFieldLoweringInfo field_info(def->field_idx); + // Mark even unresolved fields as initialized. + field_info.flags_ = MirSFieldLoweringInfo::kFlagIsStatic | + MirSFieldLoweringInfo::kFlagIsInitialized; + if (def->declaring_dex_file != 0u) { + field_info.declaring_dex_file_ = reinterpret_cast(def->declaring_dex_file); + field_info.declaring_field_idx_ = def->declaring_field_idx; + field_info.flags_ |= (def->is_volatile ? MirSFieldLoweringInfo::kFlagIsVolatile : 0u); + } + cu_.mir_graph->sfield_lowering_infos_.Insert(field_info); + } + } + + template + void PrepareSFields(const SFieldDef (&defs)[count]) { + DoPrepareSFields(defs, count); + } + + void DoPrepareBasicBlocks(const BBDef* defs, size_t count) { + cu_.mir_graph->block_id_map_.clear(); + cu_.mir_graph->block_list_.Reset(); + ASSERT_LT(3u, count); // null, entry, exit and at least one bytecode block. + ASSERT_EQ(kNullBlock, defs[0].type); + ASSERT_EQ(kEntryBlock, defs[1].type); + ASSERT_EQ(kExitBlock, defs[2].type); + for (size_t i = 0u; i != count; ++i) { + const BBDef* def = &defs[i]; + BasicBlock* bb = cu_.mir_graph->NewMemBB(def->type, i); + cu_.mir_graph->block_list_.Insert(bb); + if (def->num_successors <= 2) { + bb->successor_block_list_type = kNotUsed; + bb->successor_blocks = nullptr; + bb->fall_through = (def->num_successors >= 1) ? def->successors[0] : 0u; + bb->taken = (def->num_successors >= 2) ? def->successors[1] : 0u; + } else { + bb->successor_block_list_type = kPackedSwitch; + bb->fall_through = 0u; + bb->taken = 0u; + bb->successor_blocks = new (&cu_.arena) GrowableArray( + &cu_.arena, def->num_successors, kGrowableArraySuccessorBlocks); + for (size_t j = 0u; j != def->num_successors; ++j) { + SuccessorBlockInfo* successor_block_info = + static_cast(cu_.arena.Alloc(sizeof(SuccessorBlockInfo), + kArenaAllocSuccessor)); + successor_block_info->block = j; + successor_block_info->key = 0u; // Not used by class init check elimination. + bb->successor_blocks->Insert(successor_block_info); + } + } + bb->predecessors = new (&cu_.arena) GrowableArray( + &cu_.arena, def->num_predecessors, kGrowableArrayPredecessors); + for (size_t j = 0u; j != def->num_predecessors; ++j) { + ASSERT_NE(0u, def->predecessors[j]); + bb->predecessors->Insert(def->predecessors[j]); + } + if (def->type == kDalvikByteCode || def->type == kEntryBlock || def->type == kExitBlock) { + bb->data_flow_info = static_cast( + cu_.arena.Alloc(sizeof(BasicBlockDataFlow), kArenaAllocDFInfo)); + bb->data_flow_info->live_in_v = live_in_v_; + } + } + cu_.mir_graph->num_blocks_ = count; + ASSERT_EQ(count, cu_.mir_graph->block_list_.Size()); + cu_.mir_graph->entry_block_ = cu_.mir_graph->block_list_.Get(1); + ASSERT_EQ(kEntryBlock, cu_.mir_graph->entry_block_->block_type); + cu_.mir_graph->exit_block_ = cu_.mir_graph->block_list_.Get(2); + ASSERT_EQ(kExitBlock, cu_.mir_graph->exit_block_->block_type); + } + + template + void PrepareBasicBlocks(const BBDef (&defs)[count]) { + DoPrepareBasicBlocks(defs, count); + } + + void DoPrepareMIRs(const MIRDef* defs, size_t count) { + mir_count_ = count; + mirs_ = reinterpret_cast(cu_.arena.Alloc(sizeof(MIR) * count, kArenaAllocMIR)); + ssa_reps_.resize(count); + for (size_t i = 0u; i != count; ++i) { + const MIRDef* def = &defs[i]; + MIR* mir = &mirs_[i]; + ASSERT_LT(def->bbid, cu_.mir_graph->block_list_.Size()); + BasicBlock* bb = cu_.mir_graph->block_list_.Get(def->bbid); + bb->AppendMIR(mir); + mir->dalvikInsn.opcode = def->opcode; + mir->dalvikInsn.vB = static_cast(def->value); + mir->dalvikInsn.vB_wide = def->value; + if (def->opcode >= Instruction::IGET && def->opcode <= Instruction::IPUT_SHORT) { + ASSERT_LT(def->field_info, cu_.mir_graph->ifield_lowering_infos_.Size()); + mir->meta.ifield_lowering_info = def->field_info; + } else if (def->opcode >= Instruction::SGET && def->opcode <= Instruction::SPUT_SHORT) { + ASSERT_LT(def->field_info, cu_.mir_graph->sfield_lowering_infos_.Size()); + mir->meta.sfield_lowering_info = def->field_info; + } else if (def->opcode == static_cast(kMirOpPhi)) { + mir->meta.phi_incoming = static_cast( + allocator_->Alloc(def->num_uses * sizeof(BasicBlockId), kArenaAllocDFInfo)); + for (size_t i = 0; i != def->num_uses; ++i) { + mir->meta.phi_incoming[i] = bb->predecessors->Get(i); + } + } + mir->ssa_rep = &ssa_reps_[i]; + mir->ssa_rep->num_uses = def->num_uses; + mir->ssa_rep->uses = const_cast(def->uses); // Not modified by LVN. + mir->ssa_rep->fp_use = nullptr; // Not used by LVN. + mir->ssa_rep->num_defs = def->num_defs; + mir->ssa_rep->defs = const_cast(def->defs); // Not modified by LVN. + mir->ssa_rep->fp_def = nullptr; // Not used by LVN. + mir->dalvikInsn.opcode = def->opcode; + mir->offset = i; // LVN uses offset only for debug output + mir->optimization_flags = 0u; + } + mirs_[count - 1u].next = nullptr; + } + + template + void PrepareMIRs(const MIRDef (&defs)[count]) { + DoPrepareMIRs(defs, count); + } + + void PerformGVN() { + DoPerformGVN(); + } + + void PerformPreOrderDfsGVN() { + DoPerformGVN(); + } + + template + void DoPerformGVN() { + cu_.mir_graph->SSATransformationStart(); + cu_.mir_graph->ComputeDFSOrders(); + cu_.mir_graph->ComputeDominators(); + cu_.mir_graph->ComputeTopologicalSortOrder(); + cu_.mir_graph->SSATransformationEnd(); + ASSERT_TRUE(gvn_ == nullptr); + gvn_.reset(new (allocator_.get()) GlobalValueNumbering(&cu_, allocator_.get())); + ASSERT_FALSE(gvn_->CanModify()); + value_names_.resize(mir_count_, 0xffffu); + IteratorType iterator(cu_.mir_graph.get()); + bool change = false; + for (BasicBlock* bb = iterator.Next(change); bb != nullptr; bb = iterator.Next(change)) { + LocalValueNumbering* lvn = gvn_->PrepareBasicBlock(bb); + if (lvn != nullptr) { + for (MIR* mir = bb->first_mir_insn; mir != nullptr; mir = mir->next) { + value_names_[mir - mirs_] = lvn->GetValueNumber(mir); + } + } + change = (lvn != nullptr) && gvn_->FinishBasicBlock(bb); + ASSERT_TRUE(gvn_->Good()); + } + } + + void PerformGVNCodeModifications() { + ASSERT_TRUE(gvn_ != nullptr); + ASSERT_TRUE(gvn_->Good()); + ASSERT_FALSE(gvn_->CanModify()); + gvn_->AllowModifications(); + TopologicalSortIterator iterator(cu_.mir_graph.get()); + for (BasicBlock* bb = iterator.Next(); bb != nullptr; bb = iterator.Next()) { + LocalValueNumbering* lvn = gvn_->PrepareBasicBlock(bb); + if (lvn != nullptr) { + for (MIR* mir = bb->first_mir_insn; mir != nullptr; mir = mir->next) { + uint16_t value_name = lvn->GetValueNumber(mir); + ASSERT_EQ(value_name, value_names_[mir - mirs_]); + } + } + bool change = (lvn != nullptr) && gvn_->FinishBasicBlock(bb); + ASSERT_FALSE(change); + ASSERT_TRUE(gvn_->Good()); + } + } + + GlobalValueNumberingTest() + : pool_(), + cu_(&pool_), + mir_count_(0u), + mirs_(nullptr), + ssa_reps_(), + allocator_(), + gvn_(), + value_names_(), + live_in_v_(new (&cu_.arena) ArenaBitVector(&cu_.arena, kMaxSsaRegs, false, kBitMapMisc)) { + cu_.mir_graph.reset(new MIRGraph(&cu_, &cu_.arena)); + cu_.access_flags = kAccStatic; // Don't let "this" interfere with this test. + allocator_.reset(ScopedArenaAllocator::Create(&cu_.arena_stack)); + // Bind all possible sregs to live vregs for test purposes. + live_in_v_->SetInitialBits(kMaxSsaRegs); + cu_.mir_graph->ssa_base_vregs_ = new (&cu_.arena) GrowableArray(&cu_.arena, kMaxSsaRegs); + cu_.mir_graph->ssa_subscripts_ = new (&cu_.arena) GrowableArray(&cu_.arena, kMaxSsaRegs); + for (unsigned int i = 0; i < kMaxSsaRegs; i++) { + cu_.mir_graph->ssa_base_vregs_->Insert(i); + cu_.mir_graph->ssa_subscripts_->Insert(0); + } + } + + static constexpr size_t kMaxSsaRegs = 16384u; + + ArenaPool pool_; + CompilationUnit cu_; + size_t mir_count_; + MIR* mirs_; + std::vector ssa_reps_; + std::unique_ptr allocator_; + std::unique_ptr gvn_; + std::vector value_names_; + ArenaBitVector* live_in_v_; +}; + +class GlobalValueNumberingTestDiamond : public GlobalValueNumberingTest { + public: + GlobalValueNumberingTestDiamond(); + + private: + static const BBDef kDiamondBbs[]; +}; + +const GlobalValueNumberingTest::BBDef GlobalValueNumberingTestDiamond::kDiamondBbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(6)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(4, 5), DEF_PRED1(1)), // Block #3, top of the diamond. + DEF_BB(kDalvikByteCode, DEF_SUCC1(6), DEF_PRED1(3)), // Block #4, left side. + DEF_BB(kDalvikByteCode, DEF_SUCC1(6), DEF_PRED1(3)), // Block #5, right side. + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED2(4, 5)), // Block #6, bottom. +}; + +GlobalValueNumberingTestDiamond::GlobalValueNumberingTestDiamond() + : GlobalValueNumberingTest() { + PrepareBasicBlocks(kDiamondBbs); +} + +class GlobalValueNumberingTestLoop : public GlobalValueNumberingTest { + public: + GlobalValueNumberingTestLoop(); + + private: + static const BBDef kLoopBbs[]; +}; + +const GlobalValueNumberingTest::BBDef GlobalValueNumberingTestLoop::kLoopBbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(5)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(1)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(5, 4), DEF_PRED2(3, 4)), // "taken" loops to self. + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(4)), +}; + +GlobalValueNumberingTestLoop::GlobalValueNumberingTestLoop() + : GlobalValueNumberingTest() { + PrepareBasicBlocks(kLoopBbs); +} + +class GlobalValueNumberingTestCatch : public GlobalValueNumberingTest { + public: + GlobalValueNumberingTestCatch(); + + private: + static const BBDef kCatchBbs[]; +}; + +const GlobalValueNumberingTest::BBDef GlobalValueNumberingTestCatch::kCatchBbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(6)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(1)), // The top. + DEF_BB(kDalvikByteCode, DEF_SUCC1(6), DEF_PRED1(3)), // The throwing insn. + DEF_BB(kDalvikByteCode, DEF_SUCC1(6), DEF_PRED1(3)), // Catch handler. + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED2(4, 5)), // The merged block. +}; + +GlobalValueNumberingTestCatch::GlobalValueNumberingTestCatch() + : GlobalValueNumberingTest() { + PrepareBasicBlocks(kCatchBbs); + // Mark catch handler. + BasicBlock* catch_handler = cu_.mir_graph->GetBasicBlock(5u); + catch_handler->catch_entry = true; + // Add successor block info to the check block. + BasicBlock* check_bb = cu_.mir_graph->GetBasicBlock(3u); + check_bb->successor_block_list_type = kCatch; + check_bb->successor_blocks = new (&cu_.arena) GrowableArray( + &cu_.arena, 2, kGrowableArraySuccessorBlocks); + SuccessorBlockInfo* successor_block_info = reinterpret_cast + (cu_.arena.Alloc(sizeof(SuccessorBlockInfo), kArenaAllocSuccessor)); + successor_block_info->block = catch_handler->id; + check_bb->successor_blocks->Insert(successor_block_info); +} + +class GlobalValueNumberingTestTwoConsecutiveLoops : public GlobalValueNumberingTest { + public: + GlobalValueNumberingTestTwoConsecutiveLoops(); + + private: + static const BBDef kTwoConsecutiveLoopsBbs[]; +}; + +const GlobalValueNumberingTest::BBDef +GlobalValueNumberingTestTwoConsecutiveLoops::kTwoConsecutiveLoopsBbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(9)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(1)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(5, 6), DEF_PRED2(3, 5)), // "taken" skips over the loop. + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(4)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(7), DEF_PRED1(4)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(8, 9), DEF_PRED2(6, 8)), // "taken" skips over the loop. + DEF_BB(kDalvikByteCode, DEF_SUCC1(7), DEF_PRED1(7)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(7)), +}; + +GlobalValueNumberingTestTwoConsecutiveLoops::GlobalValueNumberingTestTwoConsecutiveLoops() + : GlobalValueNumberingTest() { + PrepareBasicBlocks(kTwoConsecutiveLoopsBbs); +} + +class GlobalValueNumberingTestTwoNestedLoops : public GlobalValueNumberingTest { + public: + GlobalValueNumberingTestTwoNestedLoops(); + + private: + static const BBDef kTwoNestedLoopsBbs[]; +}; + +const GlobalValueNumberingTest::BBDef +GlobalValueNumberingTestTwoNestedLoops::kTwoNestedLoopsBbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(8)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(1)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(5, 8), DEF_PRED2(3, 7)), // "taken" skips over the loop. + DEF_BB(kDalvikByteCode, DEF_SUCC2(6, 7), DEF_PRED2(4, 6)), // "taken" skips over the loop. + DEF_BB(kDalvikByteCode, DEF_SUCC1(5), DEF_PRED1(5)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(5)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(4)), +}; + +GlobalValueNumberingTestTwoNestedLoops::GlobalValueNumberingTestTwoNestedLoops() + : GlobalValueNumberingTest() { + PrepareBasicBlocks(kTwoNestedLoopsBbs); +} + +TEST_F(GlobalValueNumberingTestDiamond, NonAliasingIFields) { + static const IFieldDef ifields[] = { + { 0u, 1u, 0u, false }, // Int. + { 1u, 1u, 1u, false }, // Int. + { 2u, 1u, 2u, false }, // Int. + { 3u, 1u, 3u, false }, // Int. + { 4u, 1u, 4u, false }, // Short. + { 5u, 1u, 5u, false }, // Char. + { 6u, 0u, 0u, false }, // Unresolved, Short. + { 7u, 1u, 7u, false }, // Int. + { 8u, 0u, 0u, false }, // Unresolved, Int. + { 9u, 1u, 9u, false }, // Int. + { 10u, 1u, 10u, false }, // Int. + { 11u, 1u, 11u, false }, // Int. + }; + static const MIRDef mirs[] = { + // NOTE: MIRs here are ordered by unique tests. They will be put into appropriate blocks. + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 100u), + DEF_IGET(3, Instruction::IGET, 1u, 100u, 0u), + DEF_IGET(6, Instruction::IGET, 2u, 100u, 0u), // Same as at the top. + + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 200u), + DEF_IGET(4, Instruction::IGET, 4u, 200u, 1u), + DEF_IGET(6, Instruction::IGET, 5u, 200u, 1u), // Same as at the left side. + + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 300u), + DEF_IGET(3, Instruction::IGET, 7u, 300u, 2u), + DEF_CONST(5, Instruction::CONST, 8u, 1000), + DEF_IPUT(5, Instruction::IPUT, 8u, 300u, 2u), + DEF_IGET(6, Instruction::IGET, 10u, 300u, 2u), // Differs from the top and the CONST. + + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 400u), + DEF_IGET(3, Instruction::IGET, 12u, 400u, 3u), + DEF_CONST(3, Instruction::CONST, 13u, 2000), + DEF_IPUT(4, Instruction::IPUT, 13u, 400u, 3u), + DEF_IPUT(5, Instruction::IPUT, 13u, 400u, 3u), + DEF_IGET(6, Instruction::IGET, 16u, 400u, 3u), // Differs from the top, equals the CONST. + + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 500u), + DEF_IGET(3, Instruction::IGET_SHORT, 18u, 500u, 4u), + DEF_IGET(3, Instruction::IGET_CHAR, 19u, 500u, 5u), + DEF_IPUT(4, Instruction::IPUT_SHORT, 20u, 500u, 6u), // Clobbers field #4, not #5. + DEF_IGET(6, Instruction::IGET_SHORT, 21u, 500u, 4u), // Differs from the top. + DEF_IGET(6, Instruction::IGET_CHAR, 22u, 500u, 5u), // Same as the top. + + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 600u), + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 601u), + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 602u), + DEF_IGET(3, Instruction::IGET, 26u, 600u, 7u), + DEF_IGET(3, Instruction::IGET, 27u, 601u, 7u), + DEF_IPUT(4, Instruction::IPUT, 28u, 602u, 8u), // Doesn't clobber field #7 for other refs. + DEF_IGET(6, Instruction::IGET, 29u, 600u, 7u), // Same as the top. + DEF_IGET(6, Instruction::IGET, 30u, 601u, 7u), // Same as the top. + + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 700u), + DEF_CONST(4, Instruction::CONST, 32u, 3000), + DEF_IPUT(4, Instruction::IPUT, 32u, 700u, 9u), + DEF_IPUT(4, Instruction::IPUT, 32u, 700u, 10u), + DEF_CONST(5, Instruction::CONST, 35u, 3001), + DEF_IPUT(5, Instruction::IPUT, 35u, 700u, 9u), + DEF_IPUT(5, Instruction::IPUT, 35u, 700u, 10u), + DEF_IGET(6, Instruction::IGET, 38u, 700u, 9u), + DEF_IGET(6, Instruction::IGET, 39u, 700u, 10u), // Same value as read from field #9. + + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 800u), + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 801u), + DEF_CONST(4, Instruction::CONST, 42u, 3000), + DEF_IPUT(4, Instruction::IPUT, 42u, 800u, 11u), + DEF_IPUT(4, Instruction::IPUT, 42u, 801u, 11u), + DEF_CONST(5, Instruction::CONST, 45u, 3001), + DEF_IPUT(5, Instruction::IPUT, 45u, 800u, 11u), + DEF_IPUT(5, Instruction::IPUT, 45u, 801u, 11u), + DEF_IGET(6, Instruction::IGET, 48u, 800u, 11u), + DEF_IGET(6, Instruction::IGET, 49u, 801u, 11u), // Same value as read from ref 46u. + + // Invoke doesn't interfere with non-aliasing refs. There's one test above where a reference + // escapes in the left BB (we let a reference escape if we use it to store to an unresolved + // field) and the INVOKE in the right BB shouldn't interfere with that either. + DEF_INVOKE1(5, Instruction::INVOKE_STATIC, 48u), + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_EQ(value_names_[1], value_names_[2]); + + EXPECT_EQ(value_names_[4], value_names_[5]); + + EXPECT_NE(value_names_[7], value_names_[10]); + EXPECT_NE(value_names_[8], value_names_[10]); + + EXPECT_NE(value_names_[12], value_names_[16]); + EXPECT_EQ(value_names_[13], value_names_[16]); + + EXPECT_NE(value_names_[18], value_names_[21]); + EXPECT_EQ(value_names_[19], value_names_[22]); + + EXPECT_EQ(value_names_[26], value_names_[29]); + EXPECT_EQ(value_names_[27], value_names_[30]); + + EXPECT_EQ(value_names_[38], value_names_[39]); + + EXPECT_EQ(value_names_[48], value_names_[49]); +} + +TEST_F(GlobalValueNumberingTestDiamond, AliasingIFieldsSingleObject) { + static const IFieldDef ifields[] = { + { 0u, 1u, 0u, false }, // Int. + { 1u, 1u, 1u, false }, // Int. + { 2u, 1u, 2u, false }, // Int. + { 3u, 1u, 3u, false }, // Int. + { 4u, 1u, 4u, false }, // Short. + { 5u, 1u, 5u, false }, // Char. + { 6u, 0u, 0u, false }, // Unresolved, Short. + { 7u, 1u, 7u, false }, // Int. + { 8u, 1u, 8u, false }, // Int. + }; + static const MIRDef mirs[] = { + // NOTE: MIRs here are ordered by unique tests. They will be put into appropriate blocks. + DEF_IGET(3, Instruction::IGET, 0u, 100u, 0u), + DEF_IGET(6, Instruction::IGET, 1u, 100u, 0u), // Same as at the top. + + DEF_IGET(4, Instruction::IGET, 2u, 100u, 1u), + DEF_IGET(6, Instruction::IGET, 3u, 100u, 1u), // Same as at the left side. + + DEF_IGET(3, Instruction::IGET, 4u, 100u, 2u), + DEF_CONST(5, Instruction::CONST, 5u, 1000), + DEF_IPUT(5, Instruction::IPUT, 5u, 100u, 2u), + DEF_IGET(6, Instruction::IGET, 7u, 100u, 2u), // Differs from the top and the CONST. + + DEF_IGET(3, Instruction::IGET, 8u, 100u, 3u), + DEF_CONST(3, Instruction::CONST, 9u, 2000), + DEF_IPUT(4, Instruction::IPUT, 9u, 100u, 3u), + DEF_IPUT(5, Instruction::IPUT, 9u, 100u, 3u), + DEF_IGET(6, Instruction::IGET, 12u, 100u, 3u), // Differs from the top, equals the CONST. + + DEF_IGET(3, Instruction::IGET_SHORT, 13u, 100u, 4u), + DEF_IGET(3, Instruction::IGET_CHAR, 14u, 100u, 5u), + DEF_IPUT(4, Instruction::IPUT_SHORT, 15u, 100u, 6u), // Clobbers field #4, not #5. + DEF_IGET(6, Instruction::IGET_SHORT, 16u, 100u, 4u), // Differs from the top. + DEF_IGET(6, Instruction::IGET_CHAR, 17u, 100u, 5u), // Same as the top. + + DEF_CONST(4, Instruction::CONST, 18u, 3000), + DEF_IPUT(4, Instruction::IPUT, 18u, 100u, 7u), + DEF_IPUT(4, Instruction::IPUT, 18u, 100u, 8u), + DEF_CONST(5, Instruction::CONST, 21u, 3001), + DEF_IPUT(5, Instruction::IPUT, 21u, 100u, 7u), + DEF_IPUT(5, Instruction::IPUT, 21u, 100u, 8u), + DEF_IGET(6, Instruction::IGET, 24u, 100u, 7u), + DEF_IGET(6, Instruction::IGET, 25u, 100u, 8u), // Same value as read from field #7. + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_EQ(value_names_[0], value_names_[1]); + + EXPECT_EQ(value_names_[2], value_names_[3]); + + EXPECT_NE(value_names_[4], value_names_[7]); + EXPECT_NE(value_names_[5], value_names_[7]); + + EXPECT_NE(value_names_[8], value_names_[12]); + EXPECT_EQ(value_names_[9], value_names_[12]); + + EXPECT_NE(value_names_[13], value_names_[16]); + EXPECT_EQ(value_names_[14], value_names_[17]); + + EXPECT_EQ(value_names_[24], value_names_[25]); +} + +TEST_F(GlobalValueNumberingTestDiamond, AliasingIFieldsTwoObjects) { + static const IFieldDef ifields[] = { + { 0u, 1u, 0u, false }, // Int. + { 1u, 1u, 1u, false }, // Int. + { 2u, 1u, 2u, false }, // Int. + { 3u, 1u, 3u, false }, // Int. + { 4u, 1u, 4u, false }, // Short. + { 5u, 1u, 5u, false }, // Char. + { 6u, 0u, 0u, false }, // Unresolved, Short. + { 7u, 1u, 7u, false }, // Int. + { 8u, 1u, 8u, false }, // Int. + }; + static const MIRDef mirs[] = { + // NOTE: MIRs here are ordered by unique tests. They will be put into appropriate blocks. + DEF_IGET(3, Instruction::IGET, 0u, 100u, 0u), + DEF_IPUT(4, Instruction::IPUT, 1u, 101u, 0u), // May alias with the IGET at the top. + DEF_IGET(6, Instruction::IGET, 2u, 100u, 0u), // Differs from the top. + + DEF_IGET(3, Instruction::IGET, 3u, 100u, 1u), + DEF_IPUT(5, Instruction::IPUT, 3u, 101u, 1u), // If aliasing, stores the same value. + DEF_IGET(6, Instruction::IGET, 5u, 100u, 1u), // Same as the top. + + DEF_IGET(3, Instruction::IGET, 6u, 100u, 2u), + DEF_CONST(5, Instruction::CONST, 7u, 1000), + DEF_IPUT(5, Instruction::IPUT, 7u, 101u, 2u), + DEF_IGET(6, Instruction::IGET, 9u, 100u, 2u), // Differs from the top and the CONST. + + DEF_IGET(3, Instruction::IGET, 10u, 100u, 3u), + DEF_CONST(3, Instruction::CONST, 11u, 2000), + DEF_IPUT(4, Instruction::IPUT, 11u, 101u, 3u), + DEF_IPUT(5, Instruction::IPUT, 11u, 101u, 3u), + DEF_IGET(6, Instruction::IGET, 14u, 100u, 3u), // Differs from the top and the CONST. + + DEF_IGET(3, Instruction::IGET_SHORT, 15u, 100u, 4u), + DEF_IGET(3, Instruction::IGET_CHAR, 16u, 100u, 5u), + DEF_IPUT(4, Instruction::IPUT_SHORT, 17u, 101u, 6u), // Clobbers field #4, not #5. + DEF_IGET(6, Instruction::IGET_SHORT, 18u, 100u, 4u), // Differs from the top. + DEF_IGET(6, Instruction::IGET_CHAR, 19u, 100u, 5u), // Same as the top. + + DEF_CONST(4, Instruction::CONST, 20u, 3000), + DEF_IPUT(4, Instruction::IPUT, 20u, 100u, 7u), + DEF_IPUT(4, Instruction::IPUT, 20u, 101u, 8u), + DEF_CONST(5, Instruction::CONST, 23u, 3001), + DEF_IPUT(5, Instruction::IPUT, 23u, 100u, 7u), + DEF_IPUT(5, Instruction::IPUT, 23u, 101u, 8u), + DEF_IGET(6, Instruction::IGET, 26u, 100u, 7u), + DEF_IGET(6, Instruction::IGET, 27u, 101u, 8u), // Same value as read from field #7. + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_NE(value_names_[0], value_names_[2]); + + EXPECT_EQ(value_names_[3], value_names_[5]); + + EXPECT_NE(value_names_[6], value_names_[9]); + EXPECT_NE(value_names_[7], value_names_[9]); + + EXPECT_NE(value_names_[10], value_names_[14]); + EXPECT_NE(value_names_[10], value_names_[14]); + + EXPECT_NE(value_names_[15], value_names_[18]); + EXPECT_EQ(value_names_[16], value_names_[19]); + + EXPECT_EQ(value_names_[26], value_names_[27]); +} + +TEST_F(GlobalValueNumberingTestDiamond, SFields) { + static const SFieldDef sfields[] = { + { 0u, 1u, 0u, false }, // Int. + { 1u, 1u, 1u, false }, // Int. + { 2u, 1u, 2u, false }, // Int. + { 3u, 1u, 3u, false }, // Int. + { 4u, 1u, 4u, false }, // Short. + { 5u, 1u, 5u, false }, // Char. + { 6u, 0u, 0u, false }, // Unresolved, Short. + { 7u, 1u, 7u, false }, // Int. + { 8u, 1u, 8u, false }, // Int. + }; + static const MIRDef mirs[] = { + // NOTE: MIRs here are ordered by unique tests. They will be put into appropriate blocks. + DEF_SGET(3, Instruction::SGET, 0u, 0u), + DEF_SGET(6, Instruction::SGET, 1u, 0u), // Same as at the top. + + DEF_SGET(4, Instruction::SGET, 2u, 1u), + DEF_SGET(6, Instruction::SGET, 3u, 1u), // Same as at the left side. + + DEF_SGET(3, Instruction::SGET, 4u, 2u), + DEF_CONST(5, Instruction::CONST, 5u, 100), + DEF_SPUT(5, Instruction::SPUT, 5u, 2u), + DEF_SGET(6, Instruction::SGET, 7u, 2u), // Differs from the top and the CONST. + + DEF_SGET(3, Instruction::SGET, 8u, 3u), + DEF_CONST(3, Instruction::CONST, 9u, 200), + DEF_SPUT(4, Instruction::SPUT, 9u, 3u), + DEF_SPUT(5, Instruction::SPUT, 9u, 3u), + DEF_SGET(6, Instruction::SGET, 12u, 3u), // Differs from the top, equals the CONST. + + DEF_SGET(3, Instruction::SGET_SHORT, 13u, 4u), + DEF_SGET(3, Instruction::SGET_CHAR, 14u, 5u), + DEF_SPUT(4, Instruction::SPUT_SHORT, 15u, 6u), // Clobbers field #4, not #5. + DEF_SGET(6, Instruction::SGET_SHORT, 16u, 4u), // Differs from the top. + DEF_SGET(6, Instruction::SGET_CHAR, 17u, 5u), // Same as the top. + + DEF_CONST(4, Instruction::CONST, 18u, 300), + DEF_SPUT(4, Instruction::SPUT, 18u, 7u), + DEF_SPUT(4, Instruction::SPUT, 18u, 8u), + DEF_CONST(5, Instruction::CONST, 21u, 301), + DEF_SPUT(5, Instruction::SPUT, 21u, 7u), + DEF_SPUT(5, Instruction::SPUT, 21u, 8u), + DEF_SGET(6, Instruction::SGET, 24u, 7u), + DEF_SGET(6, Instruction::SGET, 25u, 8u), // Same value as read from field #7. + }; + + PrepareSFields(sfields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_EQ(value_names_[0], value_names_[1]); + + EXPECT_EQ(value_names_[2], value_names_[3]); + + EXPECT_NE(value_names_[4], value_names_[7]); + EXPECT_NE(value_names_[5], value_names_[7]); + + EXPECT_NE(value_names_[8], value_names_[12]); + EXPECT_EQ(value_names_[9], value_names_[12]); + + EXPECT_NE(value_names_[13], value_names_[16]); + EXPECT_EQ(value_names_[14], value_names_[17]); + + EXPECT_EQ(value_names_[24], value_names_[25]); +} + +TEST_F(GlobalValueNumberingTestDiamond, NonAliasingArrays) { + static const MIRDef mirs[] = { + // NOTE: MIRs here are ordered by unique tests. They will be put into appropriate blocks. + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 100u), + DEF_AGET(3, Instruction::AGET, 1u, 100u, 101u), + DEF_AGET(6, Instruction::AGET, 2u, 100u, 101u), // Same as at the top. + + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 200u), + DEF_IGET(4, Instruction::AGET, 4u, 200u, 201u), + DEF_IGET(6, Instruction::AGET, 5u, 200u, 201u), // Same as at the left side. + + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 300u), + DEF_AGET(3, Instruction::AGET, 7u, 300u, 301u), + DEF_CONST(5, Instruction::CONST, 8u, 1000), + DEF_APUT(5, Instruction::APUT, 8u, 300u, 301u), + DEF_AGET(6, Instruction::AGET, 10u, 300u, 301u), // Differs from the top and the CONST. + + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 400u), + DEF_AGET(3, Instruction::AGET, 12u, 400u, 401u), + DEF_CONST(3, Instruction::CONST, 13u, 2000), + DEF_APUT(4, Instruction::APUT, 13u, 400u, 401u), + DEF_APUT(5, Instruction::APUT, 13u, 400u, 401u), + DEF_AGET(6, Instruction::AGET, 16u, 400u, 401u), // Differs from the top, equals the CONST. + + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 500u), + DEF_AGET(3, Instruction::AGET, 18u, 500u, 501u), + DEF_APUT(4, Instruction::APUT, 19u, 500u, 502u), // Clobbers value at index 501u. + DEF_AGET(6, Instruction::AGET, 20u, 500u, 501u), // Differs from the top. + + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 600u), + DEF_CONST(4, Instruction::CONST, 22u, 3000), + DEF_APUT(4, Instruction::APUT, 22u, 600u, 601u), + DEF_APUT(4, Instruction::APUT, 22u, 600u, 602u), + DEF_CONST(5, Instruction::CONST, 25u, 3001), + DEF_APUT(5, Instruction::APUT, 25u, 600u, 601u), + DEF_APUT(5, Instruction::APUT, 25u, 600u, 602u), + DEF_AGET(6, Instruction::AGET, 28u, 600u, 601u), + DEF_AGET(6, Instruction::AGET, 29u, 600u, 602u), // Same value as read from index 601u. + + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 700u), + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 701u), + DEF_AGET(3, Instruction::AGET, 32u, 700u, 702u), + DEF_APUT(4, Instruction::APUT, 33u, 701u, 702u), // Doesn't interfere with unrelated array. + DEF_AGET(6, Instruction::AGET, 34u, 700u, 702u), // Same value as at the top. + }; + + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_EQ(value_names_[1], value_names_[2]); + + EXPECT_EQ(value_names_[4], value_names_[5]); + + EXPECT_NE(value_names_[7], value_names_[10]); + EXPECT_NE(value_names_[8], value_names_[10]); + + EXPECT_NE(value_names_[12], value_names_[16]); + EXPECT_EQ(value_names_[13], value_names_[16]); + + EXPECT_NE(value_names_[18], value_names_[20]); + + EXPECT_NE(value_names_[28], value_names_[22]); + EXPECT_NE(value_names_[28], value_names_[25]); + EXPECT_EQ(value_names_[28], value_names_[29]); + + EXPECT_EQ(value_names_[32], value_names_[34]); +} + +TEST_F(GlobalValueNumberingTestDiamond, AliasingArrays) { + static const MIRDef mirs[] = { + // NOTE: MIRs here are ordered by unique tests. They will be put into appropriate blocks. + // NOTE: We're also testing that these tests really do not interfere with each other. + + DEF_AGET(3, Instruction::AGET_BOOLEAN, 0u, 100u, 101u), + DEF_AGET(6, Instruction::AGET_BOOLEAN, 1u, 100u, 101u), // Same as at the top. + + DEF_IGET(4, Instruction::AGET_OBJECT, 2u, 200u, 201u), + DEF_IGET(6, Instruction::AGET_OBJECT, 3u, 200u, 201u), // Same as at the left side. + + DEF_AGET(3, Instruction::AGET_WIDE, 4u, 300u, 301u), + DEF_CONST(5, Instruction::CONST_WIDE, 5u, 1000), + DEF_APUT(5, Instruction::APUT_WIDE, 5u, 300u, 301u), + DEF_AGET(6, Instruction::AGET_WIDE, 7u, 300u, 301u), // Differs from the top and the CONST. + + DEF_AGET(3, Instruction::AGET_SHORT, 8u, 400u, 401u), + DEF_CONST(3, Instruction::CONST, 9u, 2000), + DEF_APUT(4, Instruction::APUT_SHORT, 9u, 400u, 401u), + DEF_APUT(5, Instruction::APUT_SHORT, 9u, 400u, 401u), + DEF_AGET(6, Instruction::AGET_SHORT, 12u, 400u, 401u), // Differs from the top, == CONST. + + DEF_AGET(3, Instruction::AGET_CHAR, 13u, 500u, 501u), + DEF_APUT(4, Instruction::APUT_CHAR, 14u, 500u, 502u), // Clobbers value at index 501u. + DEF_AGET(6, Instruction::AGET_CHAR, 15u, 500u, 501u), // Differs from the top. + + DEF_AGET(3, Instruction::AGET_BYTE, 16u, 600u, 602u), + DEF_APUT(4, Instruction::APUT_BYTE, 17u, 601u, 602u), // Clobbers values in array 600u. + DEF_AGET(6, Instruction::AGET_BYTE, 18u, 600u, 602u), // Differs from the top. + + DEF_CONST(4, Instruction::CONST, 19u, 3000), + DEF_APUT(4, Instruction::APUT, 19u, 700u, 701u), + DEF_APUT(4, Instruction::APUT, 19u, 700u, 702u), + DEF_CONST(5, Instruction::CONST, 22u, 3001), + DEF_APUT(5, Instruction::APUT, 22u, 700u, 701u), + DEF_APUT(5, Instruction::APUT, 22u, 700u, 702u), + DEF_AGET(6, Instruction::AGET, 25u, 700u, 701u), + DEF_AGET(6, Instruction::AGET, 26u, 700u, 702u), // Same value as read from index 601u. + }; + + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_EQ(value_names_[0], value_names_[1]); + + EXPECT_EQ(value_names_[2], value_names_[3]); + + EXPECT_NE(value_names_[4], value_names_[7]); + EXPECT_NE(value_names_[5], value_names_[7]); + + EXPECT_NE(value_names_[8], value_names_[12]); + EXPECT_EQ(value_names_[9], value_names_[12]); + + EXPECT_NE(value_names_[13], value_names_[15]); + + EXPECT_NE(value_names_[16], value_names_[18]); + + EXPECT_NE(value_names_[25], value_names_[19]); + EXPECT_NE(value_names_[25], value_names_[22]); + EXPECT_EQ(value_names_[25], value_names_[26]); +} + +TEST_F(GlobalValueNumberingTestDiamond, Phi) { + static const MIRDef mirs[] = { + DEF_CONST(3, Instruction::CONST, 0u, 1000), + DEF_CONST(4, Instruction::CONST, 1u, 2000), + DEF_CONST(5, Instruction::CONST, 2u, 3000), + DEF_MOVE(4, Instruction::MOVE, 3u, 0u), + DEF_MOVE(4, Instruction::MOVE, 4u, 1u), + DEF_MOVE(5, Instruction::MOVE, 5u, 0u), + DEF_MOVE(5, Instruction::MOVE, 6u, 2u), + DEF_PHI2(6, 7u, 3u, 5u), // Same as CONST 0u (1000). + DEF_PHI2(6, 8u, 3u, 0u), // Same as CONST 0u (1000). + DEF_PHI2(6, 9u, 0u, 5u), // Same as CONST 0u (1000). + DEF_PHI2(6, 10u, 4u, 5u), // Merge 1u (2000) and 0u (1000). + DEF_PHI2(6, 11u, 1u, 5u), // Merge 1u (2000) and 0u (1000). + DEF_PHI2(6, 12u, 4u, 0u), // Merge 1u (2000) and 0u (1000). + DEF_PHI2(6, 13u, 1u, 0u), // Merge 1u (2000) and 0u (1000). + DEF_PHI2(6, 14u, 3u, 6u), // Merge 0u (1000) and 2u (3000). + DEF_PHI2(6, 15u, 0u, 6u), // Merge 0u (1000) and 2u (3000). + DEF_PHI2(6, 16u, 3u, 2u), // Merge 0u (1000) and 2u (3000). + DEF_PHI2(6, 17u, 0u, 2u), // Merge 0u (1000) and 2u (3000). + DEF_PHI2(6, 18u, 4u, 6u), // Merge 1u (2000) and 2u (3000). + DEF_PHI2(6, 19u, 1u, 6u), // Merge 1u (2000) and 2u (3000). + DEF_PHI2(6, 20u, 4u, 2u), // Merge 1u (2000) and 2u (3000). + DEF_PHI2(6, 21u, 1u, 2u), // Merge 1u (2000) and 2u (3000). + }; + + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_EQ(value_names_[0], value_names_[7]); + EXPECT_EQ(value_names_[0], value_names_[8]); + EXPECT_EQ(value_names_[0], value_names_[9]); + EXPECT_NE(value_names_[10], value_names_[0]); + EXPECT_NE(value_names_[10], value_names_[1]); + EXPECT_NE(value_names_[10], value_names_[2]); + EXPECT_EQ(value_names_[10], value_names_[11]); + EXPECT_EQ(value_names_[10], value_names_[12]); + EXPECT_EQ(value_names_[10], value_names_[13]); + EXPECT_NE(value_names_[14], value_names_[0]); + EXPECT_NE(value_names_[14], value_names_[1]); + EXPECT_NE(value_names_[14], value_names_[2]); + EXPECT_NE(value_names_[14], value_names_[10]); + EXPECT_EQ(value_names_[14], value_names_[15]); + EXPECT_EQ(value_names_[14], value_names_[16]); + EXPECT_EQ(value_names_[14], value_names_[17]); + EXPECT_NE(value_names_[18], value_names_[0]); + EXPECT_NE(value_names_[18], value_names_[1]); + EXPECT_NE(value_names_[18], value_names_[2]); + EXPECT_NE(value_names_[18], value_names_[10]); + EXPECT_NE(value_names_[18], value_names_[14]); + EXPECT_EQ(value_names_[18], value_names_[19]); + EXPECT_EQ(value_names_[18], value_names_[20]); + EXPECT_EQ(value_names_[18], value_names_[21]); +} + +TEST_F(GlobalValueNumberingTestLoop, NonAliasingIFields) { + static const IFieldDef ifields[] = { + { 0u, 1u, 0u, false }, // Int. + { 1u, 1u, 1u, false }, // Int. + { 2u, 1u, 2u, false }, // Int. + { 3u, 1u, 3u, false }, // Int. + { 4u, 1u, 4u, false }, // Int. + { 5u, 1u, 5u, false }, // Short. + { 6u, 1u, 6u, false }, // Char. + { 7u, 0u, 0u, false }, // Unresolved, Short. + { 8u, 1u, 8u, false }, // Int. + { 9u, 0u, 0u, false }, // Unresolved, Int. + { 10u, 1u, 10u, false }, // Int. + { 11u, 1u, 11u, false }, // Int. + }; + static const MIRDef mirs[] = { + // NOTE: MIRs here are ordered by unique tests. They will be put into appropriate blocks. + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 100u), + DEF_IGET(3, Instruction::IGET, 1u, 100u, 0u), + DEF_IGET(4, Instruction::IGET, 2u, 100u, 0u), // Same as at the top. + DEF_IGET(5, Instruction::IGET, 3u, 100u, 0u), // Same as at the top. + + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 200u), + DEF_IGET(3, Instruction::IGET, 5u, 200u, 1u), + DEF_IGET(4, Instruction::IGET, 6u, 200u, 1u), // Differs from top... + DEF_IPUT(4, Instruction::IPUT, 7u, 200u, 1u), // Because of this IPUT. + DEF_IGET(5, Instruction::IGET, 8u, 200u, 1u), // Differs from top and the loop IGET. + + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 300u), + DEF_IGET(3, Instruction::IGET, 10u, 300u, 2u), + DEF_IPUT(4, Instruction::IPUT, 11u, 300u, 2u), // Because of this IPUT... + DEF_IGET(4, Instruction::IGET, 12u, 300u, 2u), // Differs from top. + DEF_IGET(5, Instruction::IGET, 13u, 300u, 2u), // Differs from top but same as the loop IGET. + + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 400u), + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 401u), + DEF_CONST(3, Instruction::CONST, 16u, 3000), + DEF_IPUT(3, Instruction::IPUT, 16u, 400u, 3u), + DEF_IPUT(3, Instruction::IPUT, 16u, 400u, 4u), + DEF_IPUT(3, Instruction::IPUT, 16u, 401u, 3u), + DEF_IGET(4, Instruction::IGET, 20u, 400u, 3u), // Differs from 16u and 23u. + DEF_IGET(4, Instruction::IGET, 21u, 400u, 4u), // Same as 20u. + DEF_IGET(4, Instruction::IGET, 22u, 401u, 3u), // Same as 20u. + DEF_CONST(4, Instruction::CONST, 23u, 4000), + DEF_IPUT(4, Instruction::IPUT, 23u, 400u, 3u), + DEF_IPUT(4, Instruction::IPUT, 23u, 400u, 4u), + DEF_IPUT(4, Instruction::IPUT, 23u, 401u, 3u), + DEF_IGET(5, Instruction::IGET, 27u, 400u, 3u), // Differs from 16u and 20u... + DEF_IGET(5, Instruction::IGET, 28u, 400u, 4u), // and same as the CONST 23u + DEF_IGET(5, Instruction::IGET, 29u, 400u, 4u), // and same as the CONST 23u. + + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 500u), + DEF_IGET(3, Instruction::IGET_SHORT, 31u, 500u, 5u), + DEF_IGET(3, Instruction::IGET_CHAR, 32u, 500u, 6u), + DEF_IPUT(4, Instruction::IPUT_SHORT, 33u, 500u, 7u), // Clobbers field #5, not #6. + DEF_IGET(5, Instruction::IGET_SHORT, 34u, 500u, 5u), // Differs from the top. + DEF_IGET(5, Instruction::IGET_CHAR, 35u, 500u, 6u), // Same as the top. + + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 600u), + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 601u), + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 602u), + DEF_IGET(3, Instruction::IGET, 39u, 600u, 8u), + DEF_IGET(3, Instruction::IGET, 40u, 601u, 8u), + DEF_IPUT(4, Instruction::IPUT, 41u, 602u, 9u), // Doesn't clobber field #8 for other refs. + DEF_IGET(5, Instruction::IGET, 42u, 600u, 8u), // Same as the top. + DEF_IGET(5, Instruction::IGET, 43u, 601u, 8u), // Same as the top. + + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 700u), + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 701u), + DEF_CONST(3, Instruction::CONST, 46u, 3000), + DEF_IPUT(3, Instruction::IPUT, 46u, 700u, 10u), + DEF_IPUT(3, Instruction::IPUT, 46u, 700u, 11u), + DEF_IPUT(3, Instruction::IPUT, 46u, 701u, 10u), + DEF_IGET(4, Instruction::IGET, 50u, 700u, 10u), // Differs from the CONSTs 46u and 53u. + DEF_IGET(4, Instruction::IGET, 51u, 700u, 11u), // Same as 50u. + DEF_IGET(4, Instruction::IGET, 52u, 701u, 10u), // Same as 50u. + DEF_CONST(4, Instruction::CONST, 53u, 3001), + DEF_IPUT(4, Instruction::IPUT, 53u, 700u, 10u), + DEF_IPUT(4, Instruction::IPUT, 53u, 700u, 11u), + DEF_IPUT(4, Instruction::IPUT, 53u, 701u, 10u), + DEF_IGET(5, Instruction::IGET, 57u, 700u, 10u), // Same as the CONST 53u. + DEF_IGET(5, Instruction::IGET, 58u, 700u, 11u), // Same as the CONST 53u. + DEF_IGET(5, Instruction::IGET, 59u, 701u, 10u), // Same as the CONST 53u. + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_EQ(value_names_[1], value_names_[2]); + EXPECT_EQ(value_names_[1], value_names_[3]); + + EXPECT_NE(value_names_[5], value_names_[6]); + EXPECT_NE(value_names_[5], value_names_[7]); + EXPECT_NE(value_names_[6], value_names_[7]); + + EXPECT_NE(value_names_[10], value_names_[12]); + EXPECT_EQ(value_names_[12], value_names_[13]); + + EXPECT_NE(value_names_[20], value_names_[16]); + EXPECT_NE(value_names_[20], value_names_[23]); + EXPECT_EQ(value_names_[20], value_names_[21]); + EXPECT_EQ(value_names_[20], value_names_[22]); + EXPECT_NE(value_names_[27], value_names_[16]); + EXPECT_NE(value_names_[27], value_names_[20]); + EXPECT_EQ(value_names_[27], value_names_[28]); + EXPECT_EQ(value_names_[27], value_names_[29]); + + EXPECT_NE(value_names_[31], value_names_[34]); + EXPECT_EQ(value_names_[32], value_names_[35]); + + EXPECT_EQ(value_names_[39], value_names_[42]); + EXPECT_EQ(value_names_[40], value_names_[43]); + + EXPECT_NE(value_names_[50], value_names_[46]); + EXPECT_NE(value_names_[50], value_names_[53]); + EXPECT_EQ(value_names_[50], value_names_[51]); + EXPECT_EQ(value_names_[50], value_names_[52]); + EXPECT_EQ(value_names_[57], value_names_[53]); + EXPECT_EQ(value_names_[58], value_names_[53]); + EXPECT_EQ(value_names_[59], value_names_[53]); +} + +TEST_F(GlobalValueNumberingTestLoop, AliasingIFieldsSingleObject) { + static const IFieldDef ifields[] = { + { 0u, 1u, 0u, false }, // Int. + { 1u, 1u, 1u, false }, // Int. + { 2u, 1u, 2u, false }, // Int. + { 3u, 1u, 3u, false }, // Int. + { 4u, 1u, 4u, false }, // Int. + { 5u, 1u, 5u, false }, // Short. + { 6u, 1u, 6u, false }, // Char. + { 7u, 0u, 0u, false }, // Unresolved, Short. + }; + static const MIRDef mirs[] = { + // NOTE: MIRs here are ordered by unique tests. They will be put into appropriate blocks. + DEF_IGET(3, Instruction::IGET, 0u, 100u, 0u), + DEF_IGET(4, Instruction::IGET, 1u, 100u, 0u), // Same as at the top. + DEF_IGET(5, Instruction::IGET, 2u, 100u, 0u), // Same as at the top. + + DEF_IGET(3, Instruction::IGET, 3u, 100u, 1u), + DEF_IGET(4, Instruction::IGET, 4u, 100u, 1u), // Differs from top... + DEF_IPUT(4, Instruction::IPUT, 5u, 100u, 1u), // Because of this IPUT. + DEF_IGET(5, Instruction::IGET, 6u, 100u, 1u), // Differs from top and the loop IGET. + + DEF_IGET(3, Instruction::IGET, 7u, 100u, 2u), + DEF_IPUT(4, Instruction::IPUT, 8u, 100u, 2u), // Because of this IPUT... + DEF_IGET(4, Instruction::IGET, 9u, 100u, 2u), // Differs from top. + DEF_IGET(5, Instruction::IGET, 10u, 100u, 2u), // Differs from top but same as the loop IGET. + + DEF_CONST(3, Instruction::CONST, 11u, 3000), + DEF_IPUT(3, Instruction::IPUT, 11u, 100u, 3u), + DEF_IPUT(3, Instruction::IPUT, 11u, 100u, 4u), + DEF_IGET(4, Instruction::IGET, 14u, 100u, 3u), // Differs from 11u and 16u. + DEF_IGET(4, Instruction::IGET, 15u, 100u, 4u), // Same as 14u. + DEF_CONST(4, Instruction::CONST, 16u, 4000), + DEF_IPUT(4, Instruction::IPUT, 16u, 100u, 3u), + DEF_IPUT(4, Instruction::IPUT, 16u, 100u, 4u), + DEF_IGET(5, Instruction::IGET, 19u, 100u, 3u), // Differs from 11u and 14u... + DEF_IGET(5, Instruction::IGET, 20u, 100u, 4u), // and same as the CONST 16u. + + DEF_IGET(3, Instruction::IGET_SHORT, 21u, 100u, 5u), + DEF_IGET(3, Instruction::IGET_CHAR, 22u, 100u, 6u), + DEF_IPUT(4, Instruction::IPUT_SHORT, 23u, 100u, 7u), // Clobbers field #5, not #6. + DEF_IGET(5, Instruction::IGET_SHORT, 24u, 100u, 5u), // Differs from the top. + DEF_IGET(5, Instruction::IGET_CHAR, 25u, 100u, 6u), // Same as the top. + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_EQ(value_names_[0], value_names_[1]); + EXPECT_EQ(value_names_[0], value_names_[2]); + + EXPECT_NE(value_names_[3], value_names_[4]); + EXPECT_NE(value_names_[3], value_names_[6]); + EXPECT_NE(value_names_[4], value_names_[6]); + + EXPECT_NE(value_names_[7], value_names_[9]); + EXPECT_EQ(value_names_[9], value_names_[10]); + + EXPECT_NE(value_names_[14], value_names_[11]); + EXPECT_NE(value_names_[14], value_names_[16]); + EXPECT_EQ(value_names_[14], value_names_[15]); + EXPECT_NE(value_names_[19], value_names_[11]); + EXPECT_NE(value_names_[19], value_names_[14]); + EXPECT_EQ(value_names_[19], value_names_[16]); + EXPECT_EQ(value_names_[19], value_names_[20]); + + EXPECT_NE(value_names_[21], value_names_[24]); + EXPECT_EQ(value_names_[22], value_names_[25]); +} + +TEST_F(GlobalValueNumberingTestLoop, AliasingIFieldsTwoObjects) { + static const IFieldDef ifields[] = { + { 0u, 1u, 0u, false }, // Int. + { 1u, 1u, 1u, false }, // Int. + { 2u, 1u, 2u, false }, // Int. + { 3u, 1u, 3u, false }, // Short. + { 4u, 1u, 4u, false }, // Char. + { 5u, 0u, 0u, false }, // Unresolved, Short. + { 6u, 1u, 6u, false }, // Int. + { 7u, 1u, 7u, false }, // Int. + }; + static const MIRDef mirs[] = { + // NOTE: MIRs here are ordered by unique tests. They will be put into appropriate blocks. + DEF_IGET(3, Instruction::IGET, 0u, 100u, 0u), + DEF_IPUT(4, Instruction::IPUT, 1u, 101u, 0u), // May alias with the IGET at the top. + DEF_IGET(5, Instruction::IGET, 2u, 100u, 0u), // Differs from the top. + + DEF_IGET(3, Instruction::IGET, 3u, 100u, 1u), + DEF_IPUT(4, Instruction::IPUT, 3u, 101u, 1u), // If aliasing, stores the same value. + DEF_IGET(5, Instruction::IGET, 5u, 100u, 1u), // Same as the top. + + DEF_IGET(3, Instruction::IGET, 6u, 100u, 2u), + DEF_CONST(4, Instruction::CONST, 7u, 1000), + DEF_IPUT(4, Instruction::IPUT, 7u, 101u, 2u), + DEF_IGET(5, Instruction::IGET, 9u, 100u, 2u), // Differs from the top and the CONST. + + DEF_IGET(3, Instruction::IGET_SHORT, 10u, 100u, 3u), + DEF_IGET(3, Instruction::IGET_CHAR, 11u, 100u, 4u), + DEF_IPUT(4, Instruction::IPUT_SHORT, 12u, 101u, 5u), // Clobbers field #3, not #4. + DEF_IGET(5, Instruction::IGET_SHORT, 13u, 100u, 3u), // Differs from the top. + DEF_IGET(5, Instruction::IGET_CHAR, 14u, 100u, 4u), // Same as the top. + + DEF_CONST(3, Instruction::CONST, 15u, 3000), + DEF_IPUT(3, Instruction::IPUT, 15u, 100u, 6u), + DEF_IPUT(3, Instruction::IPUT, 15u, 100u, 7u), + DEF_IPUT(3, Instruction::IPUT, 15u, 101u, 6u), + DEF_IGET(4, Instruction::IGET, 19u, 100u, 6u), // Differs from CONSTs 15u and 22u. + DEF_IGET(4, Instruction::IGET, 20u, 100u, 7u), // Same value as 19u. + DEF_IGET(4, Instruction::IGET, 21u, 101u, 6u), // Same value as read from field #7. + DEF_CONST(4, Instruction::CONST, 22u, 3001), + DEF_IPUT(4, Instruction::IPUT, 22u, 100u, 6u), + DEF_IPUT(4, Instruction::IPUT, 22u, 100u, 7u), + DEF_IPUT(4, Instruction::IPUT, 22u, 101u, 6u), + DEF_IGET(5, Instruction::IGET, 26u, 100u, 6u), // Same as CONST 22u. + DEF_IGET(5, Instruction::IGET, 27u, 100u, 7u), // Same as CONST 22u. + DEF_IGET(5, Instruction::IGET, 28u, 101u, 6u), // Same as CONST 22u. + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_NE(value_names_[0], value_names_[2]); + + EXPECT_EQ(value_names_[3], value_names_[5]); + + EXPECT_NE(value_names_[6], value_names_[9]); + EXPECT_NE(value_names_[7], value_names_[9]); + + EXPECT_NE(value_names_[10], value_names_[13]); + EXPECT_EQ(value_names_[11], value_names_[14]); + + EXPECT_NE(value_names_[19], value_names_[15]); + EXPECT_NE(value_names_[19], value_names_[22]); + EXPECT_EQ(value_names_[22], value_names_[26]); + EXPECT_EQ(value_names_[22], value_names_[27]); + EXPECT_EQ(value_names_[22], value_names_[28]); +} + +TEST_F(GlobalValueNumberingTestLoop, IFieldToBaseDependency) { + static const IFieldDef ifields[] = { + { 0u, 1u, 0u, false }, // Int. + }; + static const MIRDef mirs[] = { + // For the IGET that loads sreg 3u using base 2u, the following IPUT creates a dependency + // from the field value to the base. However, this dependency does not result in an + // infinite loop since the merge of the field value for base 0u gets assigned a value name + // based only on the base 0u, not on the actual value, and breaks the dependency cycle. + DEF_IGET(3, Instruction::IGET, 0u, 100u, 0u), + DEF_IGET(3, Instruction::IGET, 1u, 0u, 0u), + DEF_IGET(4, Instruction::IGET, 2u, 0u, 0u), + DEF_IGET(4, Instruction::IGET, 3u, 2u, 0u), + DEF_IPUT(4, Instruction::IPUT, 3u, 0u, 0u), + DEF_IGET(5, Instruction::IGET, 5u, 0u, 0u), + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_NE(value_names_[1], value_names_[2]); + EXPECT_EQ(value_names_[3], value_names_[5]); +} + +TEST_F(GlobalValueNumberingTestLoop, SFields) { + static const SFieldDef sfields[] = { + { 0u, 1u, 0u, false }, // Int. + { 1u, 1u, 1u, false }, // Int. + { 2u, 1u, 2u, false }, // Int. + }; + static const MIRDef mirs[] = { + // NOTE: MIRs here are ordered by unique tests. They will be put into appropriate blocks. + DEF_SGET(3, Instruction::SGET, 0u, 0u), + DEF_SGET(4, Instruction::SGET, 1u, 0u), // Same as at the top. + DEF_SGET(5, Instruction::SGET, 2u, 0u), // Same as at the top. + + DEF_SGET(3, Instruction::SGET, 3u, 1u), + DEF_SGET(4, Instruction::SGET, 4u, 1u), // Differs from top... + DEF_SPUT(4, Instruction::SPUT, 5u, 1u), // Because of this SPUT. + DEF_SGET(5, Instruction::SGET, 6u, 1u), // Differs from top and the loop SGET. + + DEF_SGET(3, Instruction::SGET, 7u, 2u), + DEF_SPUT(4, Instruction::SPUT, 8u, 2u), // Because of this SPUT... + DEF_SGET(4, Instruction::SGET, 9u, 2u), // Differs from top. + DEF_SGET(5, Instruction::SGET, 10u, 2u), // Differs from top but same as the loop SGET. + }; + + PrepareSFields(sfields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_EQ(value_names_[0], value_names_[1]); + EXPECT_EQ(value_names_[0], value_names_[2]); + + EXPECT_NE(value_names_[3], value_names_[4]); + EXPECT_NE(value_names_[3], value_names_[6]); + EXPECT_NE(value_names_[4], value_names_[5]); + + EXPECT_NE(value_names_[7], value_names_[9]); + EXPECT_EQ(value_names_[9], value_names_[10]); +} + +TEST_F(GlobalValueNumberingTestLoop, NonAliasingArrays) { + static const MIRDef mirs[] = { + // NOTE: MIRs here are ordered by unique tests. They will be put into appropriate blocks. + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 100u), + DEF_AGET(3, Instruction::AGET, 1u, 100u, 101u), + DEF_AGET(4, Instruction::AGET, 2u, 100u, 101u), // Same as at the top. + DEF_AGET(5, Instruction::AGET, 3u, 100u, 101u), // Same as at the top. + + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 200u), + DEF_AGET(3, Instruction::AGET, 5u, 200u, 201u), + DEF_AGET(4, Instruction::AGET, 6u, 200u, 201u), // Differs from top... + DEF_APUT(4, Instruction::APUT, 7u, 200u, 201u), // Because of this IPUT. + DEF_AGET(5, Instruction::AGET, 8u, 200u, 201u), // Differs from top and the loop AGET. + + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 300u), + DEF_AGET(3, Instruction::AGET, 10u, 300u, 301u), + DEF_APUT(4, Instruction::APUT, 11u, 300u, 301u), // Because of this IPUT... + DEF_AGET(4, Instruction::AGET, 12u, 300u, 301u), // Differs from top. + DEF_AGET(5, Instruction::AGET, 13u, 300u, 301u), // Differs from top but == the loop AGET. + + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 400u), + DEF_CONST(3, Instruction::CONST, 15u, 3000), + DEF_APUT(3, Instruction::APUT, 15u, 400u, 401u), + DEF_APUT(3, Instruction::APUT, 15u, 400u, 402u), + DEF_AGET(4, Instruction::AGET, 18u, 400u, 401u), // Differs from 15u and 20u. + DEF_AGET(4, Instruction::AGET, 19u, 400u, 402u), // Same as 18u. + DEF_CONST(4, Instruction::CONST, 20u, 4000), + DEF_APUT(4, Instruction::APUT, 20u, 400u, 401u), + DEF_APUT(4, Instruction::APUT, 20u, 400u, 402u), + DEF_AGET(5, Instruction::AGET, 23u, 400u, 401u), // Differs from 15u and 18u... + DEF_AGET(5, Instruction::AGET, 24u, 400u, 402u), // and same as the CONST 20u. + + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 500u), + DEF_AGET(3, Instruction::AGET, 26u, 500u, 501u), + DEF_APUT(4, Instruction::APUT, 27u, 500u, 502u), // Clobbers element at index 501u. + DEF_AGET(5, Instruction::AGET, 28u, 500u, 501u), // Differs from the top. + }; + + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_EQ(value_names_[1], value_names_[2]); + EXPECT_EQ(value_names_[1], value_names_[3]); + + EXPECT_NE(value_names_[5], value_names_[6]); + EXPECT_NE(value_names_[5], value_names_[8]); + EXPECT_NE(value_names_[6], value_names_[8]); + + EXPECT_NE(value_names_[10], value_names_[12]); + EXPECT_EQ(value_names_[12], value_names_[13]); + + EXPECT_NE(value_names_[18], value_names_[15]); + EXPECT_NE(value_names_[18], value_names_[20]); + EXPECT_EQ(value_names_[18], value_names_[19]); + EXPECT_NE(value_names_[23], value_names_[15]); + EXPECT_NE(value_names_[23], value_names_[18]); + EXPECT_EQ(value_names_[23], value_names_[20]); + EXPECT_EQ(value_names_[23], value_names_[24]); + + EXPECT_NE(value_names_[26], value_names_[28]); +} + +TEST_F(GlobalValueNumberingTestLoop, AliasingArrays) { + static const MIRDef mirs[] = { + // NOTE: MIRs here are ordered by unique tests. They will be put into appropriate blocks. + DEF_AGET(3, Instruction::AGET_WIDE, 0u, 100u, 101u), + DEF_AGET(4, Instruction::AGET_WIDE, 1u, 100u, 101u), // Same as at the top. + DEF_AGET(5, Instruction::AGET_WIDE, 2u, 100u, 101u), // Same as at the top. + + DEF_AGET(3, Instruction::AGET_BYTE, 3u, 200u, 201u), + DEF_AGET(4, Instruction::AGET_BYTE, 4u, 200u, 201u), // Differs from top... + DEF_APUT(4, Instruction::APUT_BYTE, 5u, 200u, 201u), // Because of this IPUT. + DEF_AGET(5, Instruction::AGET_BYTE, 6u, 200u, 201u), // Differs from top and the loop AGET. + + DEF_AGET(3, Instruction::AGET, 7u, 300u, 301u), + DEF_APUT(4, Instruction::APUT, 8u, 300u, 301u), // Because of this IPUT... + DEF_AGET(4, Instruction::AGET, 9u, 300u, 301u), // Differs from top. + DEF_AGET(5, Instruction::AGET, 10u, 300u, 301u), // Differs from top but == the loop AGET. + + DEF_CONST(3, Instruction::CONST, 11u, 3000), + DEF_APUT(3, Instruction::APUT_CHAR, 11u, 400u, 401u), + DEF_APUT(3, Instruction::APUT_CHAR, 11u, 400u, 402u), + DEF_AGET(4, Instruction::AGET_CHAR, 14u, 400u, 401u), // Differs from 11u and 16u. + DEF_AGET(4, Instruction::AGET_CHAR, 15u, 400u, 402u), // Same as 14u. + DEF_CONST(4, Instruction::CONST, 16u, 4000), + DEF_APUT(4, Instruction::APUT_CHAR, 16u, 400u, 401u), + DEF_APUT(4, Instruction::APUT_CHAR, 16u, 400u, 402u), + DEF_AGET(5, Instruction::AGET_CHAR, 19u, 400u, 401u), // Differs from 11u and 14u... + DEF_AGET(5, Instruction::AGET_CHAR, 20u, 400u, 402u), // and same as the CONST 16u. + + DEF_AGET(3, Instruction::AGET_SHORT, 21u, 500u, 501u), + DEF_APUT(4, Instruction::APUT_SHORT, 22u, 500u, 502u), // Clobbers element at index 501u. + DEF_AGET(5, Instruction::AGET_SHORT, 23u, 500u, 501u), // Differs from the top. + + DEF_AGET(3, Instruction::AGET_OBJECT, 24u, 600u, 601u), + DEF_APUT(4, Instruction::APUT_OBJECT, 25u, 601u, 602u), // Clobbers 600u/601u. + DEF_AGET(5, Instruction::AGET_OBJECT, 26u, 600u, 601u), // Differs from the top. + + DEF_AGET(3, Instruction::AGET_BOOLEAN, 27u, 700u, 701u), + DEF_APUT(4, Instruction::APUT_BOOLEAN, 27u, 701u, 702u), // Storing the same value. + DEF_AGET(5, Instruction::AGET_BOOLEAN, 29u, 700u, 701u), // Differs from the top. + }; + + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_EQ(value_names_[0], value_names_[1]); + EXPECT_EQ(value_names_[0], value_names_[2]); + + EXPECT_NE(value_names_[3], value_names_[4]); + EXPECT_NE(value_names_[3], value_names_[6]); + EXPECT_NE(value_names_[4], value_names_[6]); + + EXPECT_NE(value_names_[7], value_names_[9]); + EXPECT_EQ(value_names_[9], value_names_[10]); + + EXPECT_NE(value_names_[14], value_names_[11]); + EXPECT_NE(value_names_[14], value_names_[16]); + EXPECT_EQ(value_names_[14], value_names_[15]); + EXPECT_NE(value_names_[19], value_names_[11]); + EXPECT_NE(value_names_[19], value_names_[14]); + EXPECT_EQ(value_names_[19], value_names_[16]); + EXPECT_EQ(value_names_[19], value_names_[20]); + + EXPECT_NE(value_names_[21], value_names_[23]); + + EXPECT_NE(value_names_[24], value_names_[26]); + + EXPECT_EQ(value_names_[27], value_names_[29]); +} + +TEST_F(GlobalValueNumberingTestLoop, Phi) { + static const MIRDef mirs[] = { + DEF_CONST(3, Instruction::CONST, 0u, 1000), + DEF_PHI2(4, 1u, 0u, 6u), // Merge CONST 0u (1000) with the same. + DEF_PHI2(4, 2u, 0u, 7u), // Merge CONST 0u (1000) with the Phi itself. + DEF_PHI2(4, 3u, 0u, 8u), // Merge CONST 0u (1000) and CONST 4u (2000). + DEF_PHI2(4, 4u, 0u, 9u), // Merge CONST 0u (1000) and Phi 3u. + DEF_CONST(4, Instruction::CONST, 5u, 2000), + DEF_MOVE(4, Instruction::MOVE, 6u, 0u), + DEF_MOVE(4, Instruction::MOVE, 7u, 2u), + DEF_MOVE(4, Instruction::MOVE, 8u, 5u), + DEF_MOVE(4, Instruction::MOVE, 9u, 3u), + }; + + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_EQ(value_names_[1], value_names_[0]); + EXPECT_EQ(value_names_[2], value_names_[0]); + + EXPECT_NE(value_names_[3], value_names_[0]); + EXPECT_NE(value_names_[3], value_names_[5]); + EXPECT_NE(value_names_[4], value_names_[0]); + EXPECT_NE(value_names_[4], value_names_[5]); + EXPECT_NE(value_names_[4], value_names_[3]); +} + +TEST_F(GlobalValueNumberingTestCatch, IFields) { + static const IFieldDef ifields[] = { + { 0u, 1u, 0u, false }, + { 1u, 1u, 1u, false }, + }; + static const MIRDef mirs[] = { + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 200u), + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 201u), + DEF_IGET(3, Instruction::IGET, 2u, 100u, 0u), + DEF_IGET(3, Instruction::IGET, 3u, 200u, 0u), + DEF_IGET(3, Instruction::IGET, 4u, 201u, 0u), + DEF_INVOKE1(4, Instruction::INVOKE_STATIC, 201u), // Clobbering catch, 201u escapes. + DEF_IGET(4, Instruction::IGET, 6u, 100u, 0u), // Differs from IGET 2u. + DEF_IPUT(4, Instruction::IPUT, 6u, 100u, 1u), + DEF_IPUT(4, Instruction::IPUT, 6u, 101u, 0u), + DEF_IPUT(4, Instruction::IPUT, 6u, 200u, 0u), + DEF_IGET(5, Instruction::IGET, 10u, 100u, 0u), // Differs from IGETs 2u and 6u. + DEF_IGET(5, Instruction::IGET, 11u, 200u, 0u), // Same as the top. + DEF_IGET(5, Instruction::IGET, 12u, 201u, 0u), // Differs from the top, 201u escaped. + DEF_IPUT(5, Instruction::IPUT, 10u, 100u, 1u), + DEF_IPUT(5, Instruction::IPUT, 10u, 101u, 0u), + DEF_IPUT(5, Instruction::IPUT, 10u, 200u, 0u), + DEF_IGET(6, Instruction::IGET, 16u, 100u, 0u), // Differs from IGETs 2u, 6u and 10u. + DEF_IGET(6, Instruction::IGET, 17u, 100u, 1u), // Same as IGET 16u. + DEF_IGET(6, Instruction::IGET, 18u, 101u, 0u), // Same as IGET 16u. + DEF_IGET(6, Instruction::IGET, 19u, 200u, 0u), // Same as IGET 16u. + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_NE(value_names_[2], value_names_[6]); + EXPECT_NE(value_names_[2], value_names_[10]); + EXPECT_NE(value_names_[6], value_names_[10]); + EXPECT_EQ(value_names_[3], value_names_[11]); + EXPECT_NE(value_names_[4], value_names_[12]); + + EXPECT_NE(value_names_[2], value_names_[16]); + EXPECT_NE(value_names_[6], value_names_[16]); + EXPECT_NE(value_names_[10], value_names_[16]); + EXPECT_EQ(value_names_[16], value_names_[17]); + EXPECT_EQ(value_names_[16], value_names_[18]); + EXPECT_EQ(value_names_[16], value_names_[19]); +} + +TEST_F(GlobalValueNumberingTestCatch, SFields) { + static const SFieldDef sfields[] = { + { 0u, 1u, 0u, false }, + { 1u, 1u, 1u, false }, + }; + static const MIRDef mirs[] = { + DEF_SGET(3, Instruction::SGET, 0u, 0u), + DEF_INVOKE1(4, Instruction::INVOKE_STATIC, 100u), // Clobbering catch. + DEF_SGET(4, Instruction::SGET, 2u, 0u), // Differs from SGET 0u. + DEF_SPUT(4, Instruction::SPUT, 2u, 1u), + DEF_SGET(5, Instruction::SGET, 4u, 0u), // Differs from SGETs 0u and 2u. + DEF_SPUT(5, Instruction::SPUT, 4u, 1u), + DEF_SGET(6, Instruction::SGET, 6u, 0u), // Differs from SGETs 0u, 2u and 4u. + DEF_SGET(6, Instruction::SGET, 7u, 1u), // Same as field #1. + }; + + PrepareSFields(sfields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_NE(value_names_[0], value_names_[2]); + EXPECT_NE(value_names_[0], value_names_[4]); + EXPECT_NE(value_names_[2], value_names_[4]); + EXPECT_NE(value_names_[0], value_names_[6]); + EXPECT_NE(value_names_[2], value_names_[6]); + EXPECT_NE(value_names_[4], value_names_[6]); + EXPECT_EQ(value_names_[6], value_names_[7]); +} + +TEST_F(GlobalValueNumberingTestCatch, Arrays) { + static const MIRDef mirs[] = { + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 200u), + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 201u), + DEF_AGET(3, Instruction::AGET, 2u, 100u, 101u), + DEF_AGET(3, Instruction::AGET, 3u, 200u, 202u), + DEF_AGET(3, Instruction::AGET, 4u, 200u, 203u), + DEF_AGET(3, Instruction::AGET, 5u, 201u, 202u), + DEF_AGET(3, Instruction::AGET, 6u, 201u, 203u), + DEF_INVOKE1(4, Instruction::INVOKE_STATIC, 201u), // Clobbering catch, 201u escapes. + DEF_AGET(4, Instruction::AGET, 8u, 100u, 101u), // Differs from AGET 2u. + DEF_APUT(4, Instruction::APUT, 8u, 100u, 102u), + DEF_APUT(4, Instruction::APUT, 8u, 200u, 202u), + DEF_APUT(4, Instruction::APUT, 8u, 200u, 203u), + DEF_APUT(4, Instruction::APUT, 8u, 201u, 202u), + DEF_APUT(4, Instruction::APUT, 8u, 201u, 203u), + DEF_AGET(5, Instruction::AGET, 14u, 100u, 101u), // Differs from AGETs 2u and 8u. + DEF_AGET(5, Instruction::AGET, 15u, 200u, 202u), // Same as AGET 3u. + DEF_AGET(5, Instruction::AGET, 16u, 200u, 203u), // Same as AGET 4u. + DEF_AGET(5, Instruction::AGET, 17u, 201u, 202u), // Differs from AGET 5u. + DEF_AGET(5, Instruction::AGET, 18u, 201u, 203u), // Differs from AGET 6u. + DEF_APUT(5, Instruction::APUT, 14u, 100u, 102u), + DEF_APUT(5, Instruction::APUT, 14u, 200u, 202u), + DEF_APUT(5, Instruction::APUT, 14u, 200u, 203u), + DEF_APUT(5, Instruction::APUT, 14u, 201u, 202u), + DEF_APUT(5, Instruction::APUT, 14u, 201u, 203u), + DEF_AGET(6, Instruction::AGET, 24u, 100u, 101u), // Differs from AGETs 2u, 8u and 14u. + DEF_AGET(6, Instruction::AGET, 25u, 100u, 101u), // Same as AGET 24u. + DEF_AGET(6, Instruction::AGET, 26u, 200u, 202u), // Same as AGET 24u. + DEF_AGET(6, Instruction::AGET, 27u, 200u, 203u), // Same as AGET 24u. + DEF_AGET(6, Instruction::AGET, 28u, 201u, 202u), // Same as AGET 24u. + DEF_AGET(6, Instruction::AGET, 29u, 201u, 203u), // Same as AGET 24u. + }; + + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_NE(value_names_[2], value_names_[8]); + EXPECT_NE(value_names_[2], value_names_[14]); + EXPECT_NE(value_names_[8], value_names_[14]); + EXPECT_EQ(value_names_[3], value_names_[15]); + EXPECT_EQ(value_names_[4], value_names_[16]); + EXPECT_NE(value_names_[5], value_names_[17]); + EXPECT_NE(value_names_[6], value_names_[18]); + EXPECT_NE(value_names_[2], value_names_[24]); + EXPECT_NE(value_names_[8], value_names_[24]); + EXPECT_NE(value_names_[14], value_names_[24]); + EXPECT_EQ(value_names_[24], value_names_[25]); + EXPECT_EQ(value_names_[24], value_names_[26]); + EXPECT_EQ(value_names_[24], value_names_[27]); + EXPECT_EQ(value_names_[24], value_names_[28]); + EXPECT_EQ(value_names_[24], value_names_[29]); +} + +TEST_F(GlobalValueNumberingTestCatch, Phi) { + static const MIRDef mirs[] = { + DEF_CONST(3, Instruction::CONST, 0u, 1000), + DEF_CONST(3, Instruction::CONST, 1u, 2000), + DEF_MOVE(3, Instruction::MOVE, 2u, 1u), + DEF_INVOKE1(4, Instruction::INVOKE_STATIC, 100u), // Clobbering catch. + DEF_CONST(5, Instruction::CONST, 4u, 1000), + DEF_CONST(5, Instruction::CONST, 5u, 3000), + DEF_MOVE(5, Instruction::MOVE, 6u, 5u), + DEF_PHI2(6, 7u, 0u, 4u), + DEF_PHI2(6, 8u, 0u, 5u), + DEF_PHI2(6, 9u, 0u, 6u), + DEF_PHI2(6, 10u, 1u, 4u), + DEF_PHI2(6, 11u, 1u, 5u), + DEF_PHI2(6, 12u, 1u, 6u), + DEF_PHI2(6, 13u, 2u, 4u), + DEF_PHI2(6, 14u, 2u, 5u), + DEF_PHI2(6, 15u, 2u, 6u), + }; + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + ASSERT_EQ(value_names_[4], value_names_[0]); // Both CONSTs are 1000. + EXPECT_EQ(value_names_[7], value_names_[0]); // Merging CONST 0u and CONST 4u, both 1000. + EXPECT_NE(value_names_[8], value_names_[0]); + EXPECT_NE(value_names_[8], value_names_[5]); + EXPECT_EQ(value_names_[9], value_names_[8]); + EXPECT_NE(value_names_[10], value_names_[1]); + EXPECT_NE(value_names_[10], value_names_[4]); + EXPECT_NE(value_names_[10], value_names_[8]); + EXPECT_NE(value_names_[11], value_names_[1]); + EXPECT_NE(value_names_[11], value_names_[5]); + EXPECT_NE(value_names_[11], value_names_[8]); + EXPECT_NE(value_names_[11], value_names_[10]); + EXPECT_EQ(value_names_[12], value_names_[11]); + EXPECT_EQ(value_names_[13], value_names_[10]); + EXPECT_EQ(value_names_[14], value_names_[11]); + EXPECT_EQ(value_names_[15], value_names_[11]); +} + +TEST_F(GlobalValueNumberingTest, NullCheckIFields) { + static const IFieldDef ifields[] = { + { 0u, 1u, 0u, false }, // Object. + { 1u, 1u, 1u, false }, // Object. + }; + static const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(5)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(4, 5), DEF_PRED1(1)), // 4 is fall-through, 5 is taken. + DEF_BB(kDalvikByteCode, DEF_SUCC1(5), DEF_PRED1(3)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED2(3, 4)), + }; + static const MIRDef mirs[] = { + DEF_IGET(3, Instruction::IGET_OBJECT, 0u, 100u, 0u), + DEF_IGET(3, Instruction::IGET_OBJECT, 1u, 100u, 1u), + DEF_IGET(3, Instruction::IGET_OBJECT, 2u, 101u, 0u), + DEF_IFZ(3, Instruction::IF_NEZ, 0u), // Null-check for field #0 for taken. + DEF_UNIQUE_REF(4, Instruction::NEW_ARRAY, 4u), + DEF_IPUT(4, Instruction::IPUT_OBJECT, 4u, 100u, 0u), + DEF_IPUT(4, Instruction::IPUT_OBJECT, 4u, 100u, 1u), + DEF_IPUT(4, Instruction::IPUT_OBJECT, 4u, 101u, 0u), + DEF_IGET(5, Instruction::IGET_OBJECT, 8u, 100u, 0u), // 100u/#0, IF_NEZ/NEW_ARRAY. + DEF_IGET(5, Instruction::IGET_OBJECT, 9u, 100u, 1u), // 100u/#1, -/NEW_ARRAY. + DEF_IGET(5, Instruction::IGET_OBJECT, 10u, 101u, 0u), // 101u/#0, -/NEW_ARRAY. + DEF_CONST(5, Instruction::CONST, 11u, 0), + DEF_AGET(5, Instruction::AGET, 12u, 8u, 11u), // Null-check eliminated. + DEF_AGET(5, Instruction::AGET, 13u, 9u, 11u), // Null-check kept. + DEF_AGET(5, Instruction::AGET, 14u, 10u, 11u), // Null-check kept. + }; + static const bool expected_ignore_null_check[] = { + false, true, false, false, // BB #3; unimportant. + false, true, true, true, // BB #4; unimportant. + true, true, true, false, true, false, false, // BB #5; only the last three are important. + }; + + PrepareIFields(ifields); + PrepareBasicBlocks(bbs); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + PerformGVNCodeModifications(); + ASSERT_EQ(arraysize(expected_ignore_null_check), mir_count_); + for (size_t i = 0u; i != arraysize(mirs); ++i) { + EXPECT_EQ(expected_ignore_null_check[i], + (mirs_[i].optimization_flags & MIR_IGNORE_NULL_CHECK) != 0) << i; + } +} + +TEST_F(GlobalValueNumberingTest, NullCheckSFields) { + static const SFieldDef sfields[] = { + { 0u, 1u, 0u, false }, // Object. + { 1u, 1u, 1u, false }, // Object. + }; + static const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(5)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(4, 5), DEF_PRED1(1)), // 4 is fall-through, 5 is taken. + DEF_BB(kDalvikByteCode, DEF_SUCC1(5), DEF_PRED1(3)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED2(3, 4)), + }; + static const MIRDef mirs[] = { + DEF_SGET(3, Instruction::SGET_OBJECT, 0u, 0u), + DEF_SGET(3, Instruction::SGET_OBJECT, 1u, 1u), + DEF_IFZ(3, Instruction::IF_NEZ, 0u), // Null-check for field #0 for taken. + DEF_UNIQUE_REF(4, Instruction::NEW_ARRAY, 3u), + DEF_SPUT(4, Instruction::SPUT_OBJECT, 3u, 0u), + DEF_SPUT(4, Instruction::SPUT_OBJECT, 3u, 1u), + DEF_SGET(5, Instruction::SGET_OBJECT, 6u, 0u), // Field #0 is null-checked, IF_NEZ/NEW_ARRAY. + DEF_SGET(5, Instruction::SGET_OBJECT, 7u, 1u), // Field #1 is not null-checked, -/NEW_ARRAY. + DEF_CONST(5, Instruction::CONST, 8u, 0), + DEF_AGET(5, Instruction::AGET, 9u, 6u, 8u), // Null-check eliminated. + DEF_AGET(5, Instruction::AGET, 10u, 7u, 8u), // Null-check kept. + }; + static const bool expected_ignore_null_check[] = { + false, false, false, false, false, false, false, false, false, true, false + }; + + PrepareSFields(sfields); + PrepareBasicBlocks(bbs); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + PerformGVNCodeModifications(); + ASSERT_EQ(arraysize(expected_ignore_null_check), mir_count_); + for (size_t i = 0u; i != arraysize(mirs); ++i) { + EXPECT_EQ(expected_ignore_null_check[i], + (mirs_[i].optimization_flags & MIR_IGNORE_NULL_CHECK) != 0) << i; + } +} + +TEST_F(GlobalValueNumberingTest, NullCheckArrays) { + static const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(5)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(4, 5), DEF_PRED1(1)), // 4 is fall-through, 5 is taken. + DEF_BB(kDalvikByteCode, DEF_SUCC1(5), DEF_PRED1(3)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED2(3, 4)), + }; + static const MIRDef mirs[] = { + DEF_AGET(3, Instruction::AGET_OBJECT, 0u, 100u, 102u), + DEF_AGET(3, Instruction::AGET_OBJECT, 1u, 100u, 103u), + DEF_AGET(3, Instruction::AGET_OBJECT, 2u, 101u, 102u), + DEF_IFZ(3, Instruction::IF_NEZ, 0u), // Null-check for field #0 for taken. + DEF_UNIQUE_REF(4, Instruction::NEW_ARRAY, 4u), + DEF_APUT(4, Instruction::APUT_OBJECT, 4u, 100u, 102u), + DEF_APUT(4, Instruction::APUT_OBJECT, 4u, 100u, 103u), + DEF_APUT(4, Instruction::APUT_OBJECT, 4u, 101u, 102u), + DEF_AGET(5, Instruction::AGET_OBJECT, 8u, 100u, 102u), // Null-checked, IF_NEZ/NEW_ARRAY. + DEF_AGET(5, Instruction::AGET_OBJECT, 9u, 100u, 103u), // Not null-checked, -/NEW_ARRAY. + DEF_AGET(5, Instruction::AGET_OBJECT, 10u, 101u, 102u), // Not null-checked, -/NEW_ARRAY. + DEF_CONST(5, Instruction::CONST, 11u, 0), + DEF_AGET(5, Instruction::AGET, 12u, 8u, 11u), // Null-check eliminated. + DEF_AGET(5, Instruction::AGET, 13u, 9u, 11u), // Null-check kept. + DEF_AGET(5, Instruction::AGET, 14u, 10u, 11u), // Null-check kept. + }; + static const bool expected_ignore_null_check[] = { + false, true, false, false, // BB #3; unimportant. + false, true, true, true, // BB #4; unimportant. + true, true, true, false, true, false, false, // BB #5; only the last three are important. + }; + + PrepareBasicBlocks(bbs); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + PerformGVNCodeModifications(); + ASSERT_EQ(arraysize(expected_ignore_null_check), mir_count_); + for (size_t i = 0u; i != arraysize(mirs); ++i) { + EXPECT_EQ(expected_ignore_null_check[i], + (mirs_[i].optimization_flags & MIR_IGNORE_NULL_CHECK) != 0) << i; + } +} + +TEST_F(GlobalValueNumberingTestDiamond, RangeCheckArrays) { + // NOTE: We don't merge range checks when we merge value names for Phis or memory locations. + static const MIRDef mirs[] = { + DEF_AGET(4, Instruction::AGET, 0u, 100u, 101u), + DEF_AGET(5, Instruction::AGET, 1u, 100u, 101u), + DEF_APUT(6, Instruction::APUT, 2u, 100u, 101u), + + DEF_AGET(4, Instruction::AGET, 3u, 200u, 201u), + DEF_AGET(5, Instruction::AGET, 4u, 200u, 202u), + DEF_APUT(6, Instruction::APUT, 5u, 200u, 201u), + + DEF_AGET(4, Instruction::AGET, 6u, 300u, 302u), + DEF_AGET(5, Instruction::AGET, 7u, 301u, 302u), + DEF_APUT(6, Instruction::APUT, 8u, 300u, 302u), + }; + static const bool expected_ignore_null_check[] = { + false, false, true, + false, false, true, + false, false, false, + }; + static const bool expected_ignore_range_check[] = { + false, false, true, + false, false, false, + false, false, false, + }; + + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + PerformGVNCodeModifications(); + ASSERT_EQ(arraysize(expected_ignore_null_check), mir_count_); + ASSERT_EQ(arraysize(expected_ignore_range_check), mir_count_); + for (size_t i = 0u; i != arraysize(mirs); ++i) { + EXPECT_EQ(expected_ignore_null_check[i], + (mirs_[i].optimization_flags & MIR_IGNORE_NULL_CHECK) != 0) << i; + EXPECT_EQ(expected_ignore_range_check[i], + (mirs_[i].optimization_flags & MIR_IGNORE_RANGE_CHECK) != 0) << i; + } +} + +TEST_F(GlobalValueNumberingTestDiamond, MergeSameValueInDifferentMemoryLocations) { + static const IFieldDef ifields[] = { + { 0u, 1u, 0u, false }, // Int. + { 1u, 1u, 1u, false }, // Int. + }; + static const SFieldDef sfields[] = { + { 0u, 1u, 0u, false }, // Int. + { 1u, 1u, 1u, false }, // Int. + }; + static const MIRDef mirs[] = { + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 100u), + DEF_UNIQUE_REF(3, Instruction::NEW_ARRAY, 200u), + DEF_CONST(4, Instruction::CONST, 2u, 1000), + DEF_IPUT(4, Instruction::IPUT, 2u, 100u, 0u), + DEF_IPUT(4, Instruction::IPUT, 2u, 100u, 1u), + DEF_IPUT(4, Instruction::IPUT, 2u, 101u, 0u), + DEF_APUT(4, Instruction::APUT, 2u, 200u, 202u), + DEF_APUT(4, Instruction::APUT, 2u, 200u, 203u), + DEF_APUT(4, Instruction::APUT, 2u, 201u, 202u), + DEF_APUT(4, Instruction::APUT, 2u, 201u, 203u), + DEF_SPUT(4, Instruction::SPUT, 2u, 0u), + DEF_SPUT(4, Instruction::SPUT, 2u, 1u), + DEF_CONST(5, Instruction::CONST, 12u, 2000), + DEF_IPUT(5, Instruction::IPUT, 12u, 100u, 0u), + DEF_IPUT(5, Instruction::IPUT, 12u, 100u, 1u), + DEF_IPUT(5, Instruction::IPUT, 12u, 101u, 0u), + DEF_APUT(5, Instruction::APUT, 12u, 200u, 202u), + DEF_APUT(5, Instruction::APUT, 12u, 200u, 203u), + DEF_APUT(5, Instruction::APUT, 12u, 201u, 202u), + DEF_APUT(5, Instruction::APUT, 12u, 201u, 203u), + DEF_SPUT(5, Instruction::SPUT, 12u, 0u), + DEF_SPUT(5, Instruction::SPUT, 12u, 1u), + DEF_PHI2(6, 22u, 2u, 12u), + DEF_IGET(6, Instruction::IGET, 23u, 100u, 0u), + DEF_IGET(6, Instruction::IGET, 24u, 100u, 1u), + DEF_IGET(6, Instruction::IGET, 25u, 101u, 0u), + DEF_AGET(6, Instruction::AGET, 26u, 200u, 202u), + DEF_AGET(6, Instruction::AGET, 27u, 200u, 203u), + DEF_AGET(6, Instruction::AGET, 28u, 201u, 202u), + DEF_AGET(6, Instruction::AGET, 29u, 201u, 203u), + DEF_SGET(6, Instruction::SGET, 30u, 0u), + DEF_SGET(6, Instruction::SGET, 31u, 1u), + }; + PrepareIFields(ifields); + PrepareSFields(sfields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_NE(value_names_[2], value_names_[12]); + EXPECT_NE(value_names_[2], value_names_[22]); + EXPECT_NE(value_names_[12], value_names_[22]); + for (size_t i = 23; i != arraysize(mirs); ++i) { + EXPECT_EQ(value_names_[22], value_names_[i]) << i; + } +} + +TEST_F(GlobalValueNumberingTest, InfiniteLocationLoop) { + // This is a pattern that lead to an infinite loop during the GVN development. This has been + // fixed by rewriting the merging of AliasingValues to merge only locations read from or + // written to in each incoming LVN rather than merging all locations read from or written to + // in any incoming LVN. It also showed up only when the GVN used the DFS ordering instead of + // the "topological" ordering but, since the "topological" ordering is not really topological + // when there are cycles and an optimizing Java compiler (or a tool like proguard) could + // theoretically create any sort of flow graph, this could have shown up in real code. + // + // While we were merging all the locations: + // The first time the Phi evaluates to the same value name as CONST 0u. After the second + // evaluation, when the BB #9 has been processed, the Phi receives its own value name. + // However, the index from the first evaluation keeps disappearing and reappearing in the + // LVN's aliasing_array_value_map_'s load_value_map for BBs #9, #4, #5, #7 because of the + // DFS ordering of LVN evaluation. + static const IFieldDef ifields[] = { + { 0u, 1u, 0u, false }, // Object. + }; + static const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(4)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(1)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(5, 2), DEF_PRED2(3, 9)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(6, 7), DEF_PRED1(4)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(9), DEF_PRED1(5)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(8, 9), DEF_PRED1(5)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(9), DEF_PRED1(7)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED3(6, 7, 8)), + }; + static const MIRDef mirs[] = { + DEF_CONST(3, Instruction::CONST, 0u, 0), + DEF_PHI2(4, 1u, 0u, 10u), + DEF_INVOKE1(6, Instruction::INVOKE_STATIC, 100u), + DEF_IGET(6, Instruction::IGET_OBJECT, 3u, 100u, 0u), + DEF_CONST(6, Instruction::CONST, 4u, 1000), + DEF_APUT(6, Instruction::APUT, 4u, 3u, 1u), // Index is Phi 1u. + DEF_INVOKE1(8, Instruction::INVOKE_STATIC, 100u), + DEF_IGET(8, Instruction::IGET_OBJECT, 7u, 100u, 0u), + DEF_CONST(8, Instruction::CONST, 8u, 2000), + DEF_APUT(8, Instruction::APUT, 9u, 7u, 1u), // Index is Phi 1u. + DEF_CONST(9, Instruction::CONST, 10u, 3000), + }; + PrepareIFields(ifields); + PrepareBasicBlocks(bbs); + PrepareMIRs(mirs); + // Using DFS order for this test. The GVN result should not depend on the used ordering + // once the GVN actually converges. But creating a test for this convergence issue with + // the topological ordering could be a very challenging task. + PerformPreOrderDfsGVN(); +} + +TEST_F(GlobalValueNumberingTestTwoConsecutiveLoops, IFieldAndPhi) { + static const IFieldDef ifields[] = { + { 0u, 1u, 0u, false }, // Int. + }; + static const MIRDef mirs[] = { + DEF_MOVE(3, Instruction::MOVE_OBJECT, 0u, 100u), + DEF_IPUT(3, Instruction::IPUT_OBJECT, 0u, 200u, 0u), + DEF_PHI2(4, 2u, 0u, 3u), + DEF_MOVE(5, Instruction::MOVE_OBJECT, 3u, 300u), + DEF_IPUT(5, Instruction::IPUT_OBJECT, 3u, 200u, 0u), + DEF_MOVE(6, Instruction::MOVE_OBJECT, 5u, 2u), + DEF_IGET(6, Instruction::IGET_OBJECT, 6u, 200u, 0u), + DEF_MOVE(7, Instruction::MOVE_OBJECT, 7u, 5u), + DEF_IGET(7, Instruction::IGET_OBJECT, 8u, 200u, 0u), + DEF_MOVE(8, Instruction::MOVE_OBJECT, 9u, 5u), + DEF_IGET(8, Instruction::IGET_OBJECT, 10u, 200u, 0u), + DEF_MOVE(9, Instruction::MOVE_OBJECT, 11u, 5u), + DEF_IGET(9, Instruction::IGET_OBJECT, 12u, 200u, 0u), + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_NE(value_names_[0], value_names_[3]); + EXPECT_NE(value_names_[0], value_names_[2]); + EXPECT_NE(value_names_[3], value_names_[2]); + EXPECT_EQ(value_names_[2], value_names_[5]); + EXPECT_EQ(value_names_[5], value_names_[6]); + EXPECT_EQ(value_names_[5], value_names_[7]); + EXPECT_EQ(value_names_[5], value_names_[8]); + EXPECT_EQ(value_names_[5], value_names_[9]); + EXPECT_EQ(value_names_[5], value_names_[10]); + EXPECT_EQ(value_names_[5], value_names_[11]); + EXPECT_EQ(value_names_[5], value_names_[12]); +} + +TEST_F(GlobalValueNumberingTestTwoConsecutiveLoops, NullCheck) { + static const IFieldDef ifields[] = { + { 0u, 1u, 0u, false }, // Int. + }; + static const SFieldDef sfields[] = { + { 0u, 1u, 0u, false }, // Int. + }; + static const MIRDef mirs[] = { + DEF_MOVE(3, Instruction::MOVE_OBJECT, 0u, 100u), + DEF_IGET(3, Instruction::IGET_OBJECT, 1u, 200u, 0u), + DEF_SGET(3, Instruction::SGET_OBJECT, 2u, 0u), + DEF_AGET(3, Instruction::AGET_OBJECT, 3u, 300u, 201u), + DEF_PHI2(4, 4u, 0u, 8u), + DEF_IGET(5, Instruction::IGET_OBJECT, 5u, 200u, 0u), + DEF_SGET(5, Instruction::SGET_OBJECT, 6u, 0u), + DEF_AGET(5, Instruction::AGET_OBJECT, 7u, 300u, 201u), + DEF_MOVE(5, Instruction::MOVE_OBJECT, 8u, 400u), + DEF_IPUT(5, Instruction::IPUT_OBJECT, 4u, 200u, 0u), // PUT the Phi 4u. + DEF_SPUT(5, Instruction::SPUT_OBJECT, 4u, 0u), // PUT the Phi 4u. + DEF_APUT(5, Instruction::APUT_OBJECT, 4u, 300u, 201u), // PUT the Phi 4u. + DEF_MOVE(6, Instruction::MOVE_OBJECT, 12u, 4u), + DEF_IGET(6, Instruction::IGET_OBJECT, 13u, 200u, 0u), + DEF_SGET(6, Instruction::SGET_OBJECT, 14u, 0u), + DEF_AGET(6, Instruction::AGET_OBJECT, 15u, 300u, 201u), + DEF_AGET(6, Instruction::AGET_OBJECT, 16u, 12u, 600u), + DEF_AGET(6, Instruction::AGET_OBJECT, 17u, 13u, 600u), + DEF_AGET(6, Instruction::AGET_OBJECT, 18u, 14u, 600u), + DEF_AGET(6, Instruction::AGET_OBJECT, 19u, 15u, 600u), + DEF_MOVE(8, Instruction::MOVE_OBJECT, 20u, 12u), + DEF_IGET(8, Instruction::IGET_OBJECT, 21u, 200u, 0u), + DEF_SGET(8, Instruction::SGET_OBJECT, 22u, 0u), + DEF_AGET(8, Instruction::AGET_OBJECT, 23u, 300u, 201u), + DEF_AGET(8, Instruction::AGET_OBJECT, 24u, 12u, 600u), + DEF_AGET(8, Instruction::AGET_OBJECT, 25u, 13u, 600u), + DEF_AGET(8, Instruction::AGET_OBJECT, 26u, 14u, 600u), + DEF_AGET(8, Instruction::AGET_OBJECT, 27u, 15u, 600u), + DEF_MOVE(9, Instruction::MOVE_OBJECT, 28u, 12u), + DEF_IGET(9, Instruction::IGET_OBJECT, 29u, 200u, 0u), + DEF_SGET(9, Instruction::SGET_OBJECT, 30u, 0u), + DEF_AGET(9, Instruction::AGET_OBJECT, 31u, 300u, 201u), + DEF_AGET(9, Instruction::AGET_OBJECT, 32u, 12u, 600u), + DEF_AGET(9, Instruction::AGET_OBJECT, 33u, 13u, 600u), + DEF_AGET(9, Instruction::AGET_OBJECT, 34u, 14u, 600u), + DEF_AGET(9, Instruction::AGET_OBJECT, 35u, 15u, 600u), + }; + static const bool expected_ignore_null_check[] = { + false, false, false, false, // BB #3. + false, true, false, true, false, true, false, true, // BBs #4 and #5. + false, true, false, true, false, false, false, false, // BB #6. + false, true, false, true, true, true, true, true, // BB #7. + false, true, false, true, true, true, true, true, // BB #8. + }; + static const bool expected_ignore_range_check[] = { + false, false, false, false, // BB #3. + false, false, false, true, false, false, false, true, // BBs #4 and #5. + false, false, false, true, false, false, false, false, // BB #6. + false, false, false, true, true, true, true, true, // BB #7. + false, false, false, true, true, true, true, true, // BB #8. + }; + + PrepareIFields(ifields); + PrepareSFields(sfields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_NE(value_names_[0], value_names_[4]); + EXPECT_NE(value_names_[1], value_names_[5]); + EXPECT_NE(value_names_[2], value_names_[6]); + EXPECT_NE(value_names_[3], value_names_[7]); + EXPECT_NE(value_names_[4], value_names_[8]); + EXPECT_EQ(value_names_[4], value_names_[12]); + EXPECT_EQ(value_names_[5], value_names_[13]); + EXPECT_EQ(value_names_[6], value_names_[14]); + EXPECT_EQ(value_names_[7], value_names_[15]); + EXPECT_EQ(value_names_[12], value_names_[20]); + EXPECT_EQ(value_names_[13], value_names_[21]); + EXPECT_EQ(value_names_[14], value_names_[22]); + EXPECT_EQ(value_names_[15], value_names_[23]); + EXPECT_EQ(value_names_[12], value_names_[28]); + EXPECT_EQ(value_names_[13], value_names_[29]); + EXPECT_EQ(value_names_[14], value_names_[30]); + EXPECT_EQ(value_names_[15], value_names_[31]); + PerformGVNCodeModifications(); + for (size_t i = 0u; i != arraysize(mirs); ++i) { + EXPECT_EQ(expected_ignore_null_check[i], + (mirs_[i].optimization_flags & MIR_IGNORE_NULL_CHECK) != 0) << i; + EXPECT_EQ(expected_ignore_range_check[i], + (mirs_[i].optimization_flags & MIR_IGNORE_RANGE_CHECK) != 0) << i; + } +} + +TEST_F(GlobalValueNumberingTestTwoNestedLoops, IFieldAndPhi) { + static const IFieldDef ifields[] = { + { 0u, 1u, 0u, false }, // Int. + }; + static const MIRDef mirs[] = { + DEF_MOVE(3, Instruction::MOVE_OBJECT, 0u, 100u), + DEF_IPUT(3, Instruction::IPUT_OBJECT, 0u, 200u, 0u), + DEF_PHI2(4, 2u, 0u, 11u), + DEF_MOVE(4, Instruction::MOVE_OBJECT, 3u, 2u), + DEF_IGET(4, Instruction::IGET_OBJECT, 4u, 200u, 0u), + DEF_MOVE(5, Instruction::MOVE_OBJECT, 5u, 3u), + DEF_IGET(5, Instruction::IGET_OBJECT, 6u, 200u, 0u), + DEF_MOVE(6, Instruction::MOVE_OBJECT, 7u, 3u), + DEF_IGET(6, Instruction::IGET_OBJECT, 8u, 200u, 0u), + DEF_MOVE(7, Instruction::MOVE_OBJECT, 9u, 3u), + DEF_IGET(7, Instruction::IGET_OBJECT, 10u, 200u, 0u), + DEF_MOVE(7, Instruction::MOVE_OBJECT, 11u, 300u), + DEF_IPUT(7, Instruction::IPUT_OBJECT, 11u, 200u, 0u), + DEF_MOVE(8, Instruction::MOVE_OBJECT, 13u, 3u), + DEF_IGET(8, Instruction::IGET_OBJECT, 14u, 200u, 0u), + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformGVN(); + ASSERT_EQ(arraysize(mirs), value_names_.size()); + EXPECT_NE(value_names_[0], value_names_[11]); + EXPECT_NE(value_names_[0], value_names_[2]); + EXPECT_NE(value_names_[11], value_names_[2]); + EXPECT_EQ(value_names_[2], value_names_[3]); + EXPECT_EQ(value_names_[3], value_names_[4]); + EXPECT_EQ(value_names_[3], value_names_[5]); + EXPECT_EQ(value_names_[3], value_names_[6]); + EXPECT_EQ(value_names_[3], value_names_[7]); + EXPECT_EQ(value_names_[3], value_names_[8]); + EXPECT_EQ(value_names_[3], value_names_[9]); + EXPECT_EQ(value_names_[3], value_names_[10]); + EXPECT_EQ(value_names_[3], value_names_[13]); + EXPECT_EQ(value_names_[3], value_names_[14]); +} + +TEST_F(GlobalValueNumberingTest, NormalPathToCatchEntry) { + // When there's an empty catch block, all the exception paths lead to the next block in + // the normal path and we can also have normal "taken" or "fall-through" branches to that + // path. Check that LocalValueNumbering::PruneNonAliasingRefsForCatch() can handle it. + static const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(5)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(1)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(5), DEF_PRED1(3)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED2(3, 4)), + }; + static const MIRDef mirs[] = { + DEF_INVOKE1(4, Instruction::INVOKE_STATIC, 100u), + }; + PrepareBasicBlocks(bbs); + BasicBlock* catch_handler = cu_.mir_graph->GetBasicBlock(5u); + catch_handler->catch_entry = true; + // Add successor block info to the check block. + BasicBlock* check_bb = cu_.mir_graph->GetBasicBlock(3u); + check_bb->successor_block_list_type = kCatch; + check_bb->successor_blocks = new (&cu_.arena) GrowableArray( + &cu_.arena, 2, kGrowableArraySuccessorBlocks); + SuccessorBlockInfo* successor_block_info = reinterpret_cast + (cu_.arena.Alloc(sizeof(SuccessorBlockInfo), kArenaAllocSuccessor)); + successor_block_info->block = catch_handler->id; + check_bb->successor_blocks->Insert(successor_block_info); + BasicBlock* merge_block = cu_.mir_graph->GetBasicBlock(4u); + std::swap(merge_block->taken, merge_block->fall_through); + PrepareMIRs(mirs); + PerformGVN(); +} + +} // namespace art diff --git a/compiler/dex/local_value_numbering.cc b/compiler/dex/local_value_numbering.cc index 35d29235f2c..59975682164 100644 --- a/compiler/dex/local_value_numbering.cc +++ b/compiler/dex/local_value_numbering.cc @@ -16,11 +16,1334 @@ #include "local_value_numbering.h" +#include "global_value_numbering.h" +#include "mir_field_info.h" +#include "mir_graph.h" + namespace art { +namespace { // anonymous namespace + +// Operations used for value map keys instead of actual opcode. +static constexpr uint16_t kInvokeMemoryVersionBumpOp = Instruction::INVOKE_VIRTUAL; +static constexpr uint16_t kUnresolvedSFieldOp = Instruction::SGET; +static constexpr uint16_t kResolvedSFieldOp = Instruction::SGET_WIDE; +static constexpr uint16_t kUnresolvedIFieldOp = Instruction::IGET; +static constexpr uint16_t kNonAliasingIFieldLocOp = Instruction::IGET_WIDE; +static constexpr uint16_t kNonAliasingIFieldInitialOp = Instruction::IGET_OBJECT; +static constexpr uint16_t kAliasingIFieldOp = Instruction::IGET_BOOLEAN; +static constexpr uint16_t kAliasingIFieldStartVersionOp = Instruction::IGET_BYTE; +static constexpr uint16_t kAliasingIFieldBumpVersionOp = Instruction::IGET_CHAR; +static constexpr uint16_t kNonAliasingArrayOp = Instruction::AGET; +static constexpr uint16_t kNonAliasingArrayStartVersionOp = Instruction::AGET_WIDE; +static constexpr uint16_t kNonAliasingArrayBumpVersionOp = Instruction::AGET_OBJECT; +static constexpr uint16_t kAliasingArrayOp = Instruction::AGET_BOOLEAN; +static constexpr uint16_t kAliasingArrayStartVersionOp = Instruction::AGET_BYTE; +static constexpr uint16_t kAliasingArrayBumpVersionOp = Instruction::AGET_CHAR; +static constexpr uint16_t kMergeBlockMemoryVersionBumpOp = Instruction::INVOKE_VIRTUAL_RANGE; +static constexpr uint16_t kMergeBlockAliasingIFieldVersionBumpOp = Instruction::IPUT; +static constexpr uint16_t kMergeBlockAliasingIFieldMergeLocationOp = Instruction::IPUT_WIDE; +static constexpr uint16_t kMergeBlockNonAliasingArrayVersionBumpOp = Instruction::APUT; +static constexpr uint16_t kMergeBlockNonAliasingArrayMergeLocationOp = Instruction::APUT_WIDE; +static constexpr uint16_t kMergeBlockAliasingArrayVersionBumpOp = Instruction::APUT_OBJECT; +static constexpr uint16_t kMergeBlockAliasingArrayMergeLocationOp = Instruction::APUT_BOOLEAN; +static constexpr uint16_t kMergeBlockNonAliasingIFieldVersionBumpOp = Instruction::APUT_BYTE; +static constexpr uint16_t kMergeBlockSFieldVersionBumpOp = Instruction::APUT_CHAR; + +} // anonymous namespace + +class LocalValueNumbering::AliasingIFieldVersions { + public: + static uint16_t StartMemoryVersion(GlobalValueNumbering* gvn, const LocalValueNumbering* lvn, + uint16_t field_id) { + uint16_t type = gvn->GetFieldType(field_id); + return gvn->LookupValue(kAliasingIFieldStartVersionOp, field_id, + lvn->global_memory_version_, lvn->unresolved_ifield_version_[type]); + } + + static uint16_t BumpMemoryVersion(GlobalValueNumbering* gvn, uint16_t old_version, + uint16_t store_ref_set_id, uint16_t stored_value) { + return gvn->LookupValue(kAliasingIFieldBumpVersionOp, old_version, + store_ref_set_id, stored_value); + } + + static uint16_t LookupGlobalValue(GlobalValueNumbering* gvn, + uint16_t field_id, uint16_t base, uint16_t memory_version) { + return gvn->LookupValue(kAliasingIFieldOp, field_id, base, memory_version); + } + + static uint16_t LookupMergeValue(GlobalValueNumbering* gvn, const LocalValueNumbering* lvn, + uint16_t field_id, uint16_t base) { + // If the base/field_id is non-aliasing in lvn, use the non-aliasing value. + uint16_t type = gvn->GetFieldType(field_id); + if (lvn->IsNonAliasingIField(base, field_id, type)) { + uint16_t loc = gvn->LookupValue(kNonAliasingIFieldLocOp, base, field_id, type); + auto lb = lvn->non_aliasing_ifield_value_map_.find(loc); + return (lb != lvn->non_aliasing_ifield_value_map_.end()) + ? lb->second + : gvn->LookupValue(kNonAliasingIFieldInitialOp, loc, kNoValue, kNoValue); + } + return AliasingValuesMergeGet( + gvn, lvn, &lvn->aliasing_ifield_value_map_, field_id, base); + } + + static bool HasNewBaseVersion(GlobalValueNumbering* gvn, const LocalValueNumbering* lvn, + uint16_t field_id) { + uint16_t type = gvn->GetFieldType(field_id); + return lvn->unresolved_ifield_version_[type] == lvn->merge_new_memory_version_ || + lvn->global_memory_version_ == lvn->merge_new_memory_version_; + } + + static uint16_t LookupMergeBlockValue(GlobalValueNumbering* gvn, uint16_t lvn_id, + uint16_t field_id) { + return gvn->LookupValue(kMergeBlockAliasingIFieldVersionBumpOp, field_id, kNoValue, lvn_id); + } + + static uint16_t LookupMergeLocationValue(GlobalValueNumbering* gvn, uint16_t lvn_id, + uint16_t field_id, uint16_t base) { + return gvn->LookupValue(kMergeBlockAliasingIFieldMergeLocationOp, field_id, base, lvn_id); + } +}; + +class LocalValueNumbering::NonAliasingArrayVersions { + public: + static uint16_t StartMemoryVersion(GlobalValueNumbering* gvn, const LocalValueNumbering* lvn, + uint16_t array) { + return gvn->LookupValue(kNonAliasingArrayStartVersionOp, array, kNoValue, kNoValue); + } + + static uint16_t BumpMemoryVersion(GlobalValueNumbering* gvn, uint16_t old_version, + uint16_t store_ref_set_id, uint16_t stored_value) { + return gvn->LookupValue(kNonAliasingArrayBumpVersionOp, old_version, + store_ref_set_id, stored_value); + } + + static uint16_t LookupGlobalValue(GlobalValueNumbering* gvn, + uint16_t array, uint16_t index, uint16_t memory_version) { + return gvn->LookupValue(kNonAliasingArrayOp, array, index, memory_version); + } + + static uint16_t LookupMergeValue(GlobalValueNumbering* gvn, const LocalValueNumbering* lvn, + uint16_t array, uint16_t index) { + return AliasingValuesMergeGet( + gvn, lvn, &lvn->non_aliasing_array_value_map_, array, index); + } + + static bool HasNewBaseVersion(GlobalValueNumbering* gvn, const LocalValueNumbering* lvn, + uint16_t array) { + return false; // Not affected by global_memory_version_. + } + + static uint16_t LookupMergeBlockValue(GlobalValueNumbering* gvn, uint16_t lvn_id, + uint16_t array) { + return gvn->LookupValue(kMergeBlockNonAliasingArrayVersionBumpOp, array, kNoValue, lvn_id); + } + + static uint16_t LookupMergeLocationValue(GlobalValueNumbering* gvn, uint16_t lvn_id, + uint16_t array, uint16_t index) { + return gvn->LookupValue(kMergeBlockNonAliasingArrayMergeLocationOp, array, index, lvn_id); + } +}; + +class LocalValueNumbering::AliasingArrayVersions { + public: + static uint16_t StartMemoryVersion(GlobalValueNumbering* gvn, const LocalValueNumbering* lvn, + uint16_t type) { + return gvn->LookupValue(kAliasingArrayStartVersionOp, type, lvn->global_memory_version_, + kNoValue); + } + + static uint16_t BumpMemoryVersion(GlobalValueNumbering* gvn, uint16_t old_version, + uint16_t store_ref_set_id, uint16_t stored_value) { + return gvn->LookupValue(kAliasingArrayBumpVersionOp, old_version, + store_ref_set_id, stored_value); + } + + static uint16_t LookupGlobalValue(GlobalValueNumbering* gvn, + uint16_t type, uint16_t location, uint16_t memory_version) { + return gvn->LookupValue(kAliasingArrayOp, type, location, memory_version); + } + + static uint16_t LookupMergeValue(GlobalValueNumbering* gvn, const LocalValueNumbering* lvn, + uint16_t type, uint16_t location) { + // If the location is non-aliasing in lvn, use the non-aliasing value. + uint16_t array = gvn->GetArrayLocationBase(location); + if (lvn->IsNonAliasingArray(array, type)) { + uint16_t index = gvn->GetArrayLocationIndex(location); + return NonAliasingArrayVersions::LookupMergeValue(gvn, lvn, array, index); + } + return AliasingValuesMergeGet( + gvn, lvn, &lvn->aliasing_array_value_map_, type, location); + } + + static bool HasNewBaseVersion(GlobalValueNumbering* gvn, const LocalValueNumbering* lvn, + uint16_t type) { + return lvn->global_memory_version_ == lvn->merge_new_memory_version_; + } + + static uint16_t LookupMergeBlockValue(GlobalValueNumbering* gvn, uint16_t lvn_id, + uint16_t type) { + return gvn->LookupValue(kMergeBlockAliasingArrayVersionBumpOp, type, kNoValue, lvn_id); + } + + static uint16_t LookupMergeLocationValue(GlobalValueNumbering* gvn, uint16_t lvn_id, + uint16_t type, uint16_t location) { + return gvn->LookupValue(kMergeBlockAliasingArrayMergeLocationOp, type, location, lvn_id); + } +}; + +template +LocalValueNumbering::AliasingValues* LocalValueNumbering::GetAliasingValues( + Map* map, const typename Map::key_type& key) { + auto lb = map->lower_bound(key); + if (lb == map->end() || map->key_comp()(key, lb->first)) { + lb = map->PutBefore(lb, key, AliasingValues(this)); + } + return &lb->second; +} + +template +void LocalValueNumbering::UpdateAliasingValuesLoadVersion(const KeyType& key, + AliasingValues* values) { + if (values->last_load_memory_version == kNoValue) { + // Get the start version that accounts for aliasing with unresolved fields of the same + // type and make it unique for the field by including the field_id. + uint16_t memory_version = values->memory_version_before_stores; + if (memory_version == kNoValue) { + memory_version = Versions::StartMemoryVersion(gvn_, this, key); + } + if (!values->store_loc_set.empty()) { + uint16_t ref_set_id = gvn_->GetRefSetId(values->store_loc_set); + memory_version = Versions::BumpMemoryVersion(gvn_, memory_version, ref_set_id, + values->last_stored_value); + } + values->last_load_memory_version = memory_version; + } +} + +template +uint16_t LocalValueNumbering::AliasingValuesMergeGet(GlobalValueNumbering* gvn, + const LocalValueNumbering* lvn, + Map* map, const typename Map::key_type& key, + uint16_t location) { + // Retrieve the value name that we would get from + // const_cast(lvn)->HandleAliasingValueGet(map. key, location) + // but don't modify the map. + uint16_t value_name; + auto it = map->find(key); + if (it == map->end()) { + uint16_t start_version = Versions::StartMemoryVersion(gvn, lvn, key); + value_name = Versions::LookupGlobalValue(gvn, key, location, start_version); + } else if (it->second.store_loc_set.count(location) != 0u) { + value_name = it->second.last_stored_value; + } else { + auto load_it = it->second.load_value_map.find(location); + if (load_it != it->second.load_value_map.end()) { + value_name = load_it->second; + } else { + value_name = Versions::LookupGlobalValue(gvn, key, location, it->second.last_load_memory_version); + } + } + return value_name; +} + +template +uint16_t LocalValueNumbering::HandleAliasingValuesGet(Map* map, const typename Map::key_type& key, + uint16_t location) { + // Retrieve the value name for IGET/SGET/AGET, update the map with new value if any. + uint16_t res; + AliasingValues* values = GetAliasingValues(map, key); + if (values->store_loc_set.count(location) != 0u) { + res = values->last_stored_value; + } else { + UpdateAliasingValuesLoadVersion(key, values); + auto lb = values->load_value_map.lower_bound(location); + if (lb != values->load_value_map.end() && lb->first == location) { + res = lb->second; + } else { + res = Versions::LookupGlobalValue(gvn_, key, location, values->last_load_memory_version); + values->load_value_map.PutBefore(lb, location, res); + } + } + return res; +} + +template +bool LocalValueNumbering::HandleAliasingValuesPut(Map* map, const typename Map::key_type& key, + uint16_t location, uint16_t value) { + AliasingValues* values = GetAliasingValues(map, key); + auto load_values_it = values->load_value_map.find(location); + if (load_values_it != values->load_value_map.end() && load_values_it->second == value) { + // This insn can be eliminated, it stores the same value that's already in the field. + return false; + } + if (value == values->last_stored_value) { + auto store_loc_lb = values->store_loc_set.lower_bound(location); + if (store_loc_lb != values->store_loc_set.end() && *store_loc_lb == location) { + // This insn can be eliminated, it stores the same value that's already in the field. + return false; + } + values->store_loc_set.emplace_hint(store_loc_lb, location); + } else { + UpdateAliasingValuesLoadVersion(key, values); + values->memory_version_before_stores = values->last_load_memory_version; + values->last_stored_value = value; + values->store_loc_set.clear(); + values->store_loc_set.insert(location); + } + // Clear the last load memory version and remove all potentially overwritten values. + values->last_load_memory_version = kNoValue; + auto it = values->load_value_map.begin(), end = values->load_value_map.end(); + while (it != end) { + if (it->second == value) { + ++it; + } else { + it = values->load_value_map.erase(it); + } + } + return true; +} + +template +void LocalValueNumbering::CopyAliasingValuesMap(ScopedArenaSafeMap* dest, + const ScopedArenaSafeMap& src) { + // We need each new AliasingValues (or rather its map members) to be constructed + // with our allocator, rather than the allocator of the source. + for (const auto& entry : src) { + auto it = dest->PutBefore(dest->end(), entry.first, AliasingValues(this)); + it->second = entry.second; // Map assignments preserve current allocator. + } +} + +LocalValueNumbering::LocalValueNumbering(GlobalValueNumbering* gvn, uint16_t id, + ScopedArenaAllocator* allocator) + : gvn_(gvn), + id_(id), + sreg_value_map_(std::less(), allocator->Adapter()), + sreg_wide_value_map_(std::less(), allocator->Adapter()), + sfield_value_map_(std::less(), allocator->Adapter()), + non_aliasing_ifield_value_map_(std::less(), allocator->Adapter()), + aliasing_ifield_value_map_(std::less(), allocator->Adapter()), + non_aliasing_array_value_map_(std::less(), allocator->Adapter()), + aliasing_array_value_map_(std::less(), allocator->Adapter()), + global_memory_version_(0u), + non_aliasing_refs_(std::less(), allocator->Adapter()), + escaped_refs_(std::less(), allocator->Adapter()), + escaped_ifield_clobber_set_(EscapedIFieldClobberKeyComparator(), allocator->Adapter()), + escaped_array_clobber_set_(EscapedArrayClobberKeyComparator(), allocator->Adapter()), + range_checked_(RangeCheckKeyComparator() , allocator->Adapter()), + null_checked_(std::less(), allocator->Adapter()), + merge_names_(allocator->Adapter()), + merge_map_(std::less>(), allocator->Adapter()), + merge_new_memory_version_(kNoValue) { + std::fill_n(unresolved_sfield_version_, kFieldTypeCount, 0u); + std::fill_n(unresolved_ifield_version_, kFieldTypeCount, 0u); +} + +bool LocalValueNumbering::Equals(const LocalValueNumbering& other) const { + DCHECK(gvn_ == other.gvn_); + // Compare the maps/sets and memory versions. + return sreg_value_map_ == other.sreg_value_map_ && + sreg_wide_value_map_ == other.sreg_wide_value_map_ && + sfield_value_map_ == other.sfield_value_map_ && + non_aliasing_ifield_value_map_ == other.non_aliasing_ifield_value_map_ && + aliasing_ifield_value_map_ == other.aliasing_ifield_value_map_ && + non_aliasing_array_value_map_ == other.non_aliasing_array_value_map_ && + aliasing_array_value_map_ == other.aliasing_array_value_map_ && + SameMemoryVersion(other) && + non_aliasing_refs_ == other.non_aliasing_refs_ && + escaped_refs_ == other.escaped_refs_ && + escaped_ifield_clobber_set_ == other.escaped_ifield_clobber_set_ && + escaped_array_clobber_set_ == other.escaped_array_clobber_set_ && + range_checked_ == other.range_checked_ && + null_checked_ == other.null_checked_; +} + +void LocalValueNumbering::MergeOne(const LocalValueNumbering& other, MergeType merge_type) { + CopyLiveSregValues(&sreg_value_map_, other.sreg_value_map_); + CopyLiveSregValues(&sreg_wide_value_map_, other.sreg_wide_value_map_); + + if (merge_type == kReturnMerge) { + // RETURN or PHI+RETURN. We need only sreg value maps. + return; + } + + non_aliasing_ifield_value_map_ = other.non_aliasing_ifield_value_map_; + CopyAliasingValuesMap(&non_aliasing_array_value_map_, other.non_aliasing_array_value_map_); + non_aliasing_refs_ = other.non_aliasing_refs_; + range_checked_ = other.range_checked_; + null_checked_ = other.null_checked_; + + if (merge_type == kCatchMerge) { + // Memory is clobbered. Use new memory version and don't merge aliasing locations. + global_memory_version_ = NewMemoryVersion(&merge_new_memory_version_); + std::fill_n(unresolved_sfield_version_, kFieldTypeCount, global_memory_version_); + std::fill_n(unresolved_ifield_version_, kFieldTypeCount, global_memory_version_); + PruneNonAliasingRefsForCatch(); + return; + } + + DCHECK(merge_type == kNormalMerge); + global_memory_version_ = other.global_memory_version_; + std::copy_n(other.unresolved_ifield_version_, kFieldTypeCount, unresolved_ifield_version_); + std::copy_n(other.unresolved_sfield_version_, kFieldTypeCount, unresolved_sfield_version_); + sfield_value_map_ = other.sfield_value_map_; + CopyAliasingValuesMap(&aliasing_ifield_value_map_, other.aliasing_ifield_value_map_); + CopyAliasingValuesMap(&aliasing_array_value_map_, other.aliasing_array_value_map_); + escaped_refs_ = other.escaped_refs_; + escaped_ifield_clobber_set_ = other.escaped_ifield_clobber_set_; + escaped_array_clobber_set_ = other.escaped_array_clobber_set_; +} + +bool LocalValueNumbering::SameMemoryVersion(const LocalValueNumbering& other) const { + return + global_memory_version_ == other.global_memory_version_ && + std::equal(unresolved_ifield_version_, unresolved_ifield_version_ + kFieldTypeCount, + other.unresolved_ifield_version_) && + std::equal(unresolved_sfield_version_, unresolved_sfield_version_ + kFieldTypeCount, + other.unresolved_sfield_version_); +} + +uint16_t LocalValueNumbering::NewMemoryVersion(uint16_t* new_version) { + if (*new_version == kNoValue) { + *new_version = gvn_->LookupValue(kMergeBlockMemoryVersionBumpOp, 0u, 0u, id_); + } + return *new_version; +} + +void LocalValueNumbering::MergeMemoryVersions(bool clobbered_catch) { + DCHECK_GE(gvn_->merge_lvns_.size(), 2u); + const LocalValueNumbering* cmp = gvn_->merge_lvns_[0]; + // Check if the global version has changed. + bool new_global_version = clobbered_catch; + if (!new_global_version) { + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + if (lvn->global_memory_version_ != cmp->global_memory_version_) { + // Use a new version for everything. + new_global_version = true; + break; + } + } + } + if (new_global_version) { + global_memory_version_ = NewMemoryVersion(&merge_new_memory_version_); + std::fill_n(unresolved_sfield_version_, kFieldTypeCount, merge_new_memory_version_); + std::fill_n(unresolved_ifield_version_, kFieldTypeCount, merge_new_memory_version_); + } else { + // Initialize with a copy of memory versions from the comparison LVN. + global_memory_version_ = cmp->global_memory_version_; + std::copy_n(cmp->unresolved_ifield_version_, kFieldTypeCount, unresolved_ifield_version_); + std::copy_n(cmp->unresolved_sfield_version_, kFieldTypeCount, unresolved_sfield_version_); + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + if (lvn == cmp) { + continue; + } + for (size_t i = 0; i != kFieldTypeCount; ++i) { + if (lvn->unresolved_ifield_version_[i] != cmp->unresolved_ifield_version_[i]) { + unresolved_ifield_version_[i] = NewMemoryVersion(&merge_new_memory_version_); + } + if (lvn->unresolved_sfield_version_[i] != cmp->unresolved_sfield_version_[i]) { + unresolved_sfield_version_[i] = NewMemoryVersion(&merge_new_memory_version_); + } + } + } + } +} + +void LocalValueNumbering::PruneNonAliasingRefsForCatch() { + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + const BasicBlock* bb = gvn_->GetBasicBlock(lvn->Id()); + if (UNLIKELY(bb->taken == id_) || UNLIKELY(bb->fall_through == id_)) { + // Non-exceptional path to a catch handler means that the catch block was actually + // empty and all exceptional paths lead to the shared path after that empty block. + continue; + } + DCHECK_EQ(bb->taken, kNullBlock); + DCHECK_NE(bb->fall_through, kNullBlock); + const BasicBlock* fall_through_bb = gvn_->GetBasicBlock(bb->fall_through); + const MIR* mir = fall_through_bb->first_mir_insn; + DCHECK(mir != nullptr); + // Only INVOKEs can leak and clobber non-aliasing references if they throw. + if ((Instruction::FlagsOf(mir->dalvikInsn.opcode) & Instruction::kInvoke) != 0) { + for (uint16_t i = 0u; i != mir->ssa_rep->num_uses; ++i) { + uint16_t value_name = lvn->GetOperandValue(mir->ssa_rep->uses[i]); + non_aliasing_refs_.erase(value_name); + } + } + } +} + + +template +void LocalValueNumbering::IntersectSets() { + DCHECK_GE(gvn_->merge_lvns_.size(), 2u); + + // Find the LVN with the least entries in the set. + const LocalValueNumbering* least_entries_lvn = gvn_->merge_lvns_[0]; + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + if ((lvn->*set_ptr).size() < (least_entries_lvn->*set_ptr).size()) { + least_entries_lvn = lvn; + } + } + + // For each key check if it's in all the LVNs. + for (const auto& key : least_entries_lvn->*set_ptr) { + bool checked = true; + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + if (lvn != least_entries_lvn && (lvn->*set_ptr).count(key) == 0u) { + checked = false; + break; + } + } + if (checked) { + (this->*set_ptr).emplace_hint((this->*set_ptr).end(), key); + } + } +} + +void LocalValueNumbering::CopyLiveSregValues(SregValueMap* dest, const SregValueMap& src) { + auto dest_end = dest->end(); + ArenaBitVector* live_in_v = gvn_->GetMirGraph()->GetBasicBlock(id_)->data_flow_info->live_in_v; + DCHECK(live_in_v != nullptr); + for (const auto& entry : src) { + bool live = live_in_v->IsBitSet(gvn_->GetMirGraph()->SRegToVReg(entry.first)); + if (live) { + dest->PutBefore(dest_end, entry.first, entry.second); + } + } +} + +template +void LocalValueNumbering::IntersectSregValueMaps() { + DCHECK_GE(gvn_->merge_lvns_.size(), 2u); + + // Find the LVN with the least entries in the set. + const LocalValueNumbering* least_entries_lvn = gvn_->merge_lvns_[0]; + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + if ((lvn->*map_ptr).size() < (least_entries_lvn->*map_ptr).size()) { + least_entries_lvn = lvn; + } + } + + // For each key check if it's in all the LVNs. + ArenaBitVector* live_in_v = gvn_->GetMirGraph()->GetBasicBlock(id_)->data_flow_info->live_in_v; + DCHECK(live_in_v != nullptr); + for (const auto& entry : least_entries_lvn->*map_ptr) { + bool live_and_same = live_in_v->IsBitSet(gvn_->GetMirGraph()->SRegToVReg(entry.first)); + if (live_and_same) { + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + if (lvn != least_entries_lvn) { + auto it = (lvn->*map_ptr).find(entry.first); + if (it == (lvn->*map_ptr).end() || !(it->second == entry.second)) { + live_and_same = false; + break; + } + } + } + } + if (live_and_same) { + (this->*map_ptr).PutBefore((this->*map_ptr).end(), entry.first, entry.second); + } + } +} + +// Intersect maps as sets. The value type must be equality-comparable. +template +void LocalValueNumbering::InPlaceIntersectMaps(Map* work_map, const Map& other_map) { + auto work_it = work_map->begin(), work_end = work_map->end(); + auto cmp = work_map->value_comp(); + for (const auto& entry : other_map) { + while (work_it != work_end && + (cmp(*work_it, entry) || + (!cmp(entry, *work_it) && !(work_it->second == entry.second)))) { + work_it = work_map->erase(work_it); + } + if (work_it == work_end) { + return; + } + ++work_it; + } +} + +template +void LocalValueNumbering::MergeSets() { + auto cmp = (this->*set_ptr).value_comp(); + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + auto my_it = (this->*set_ptr).begin(), my_end = (this->*set_ptr).end(); + for (const auto& entry : lvn->*set_ptr) { + while (my_it != my_end && cmp(*my_it, entry)) { + ++my_it; + } + if (my_it != my_end && !cmp(entry, *my_it)) { + // Already handled. + ++my_it; + } else { + // Merge values for this field_id. + (this->*MergeFn)(entry, my_it); // my_it remains valid across inserts to std::set/SafeMap. + } + } + } +} + +void LocalValueNumbering::IntersectAliasingValueLocations(AliasingValues* work_values, + const AliasingValues* values) { + auto cmp = work_values->load_value_map.key_comp(); + auto work_it = work_values->load_value_map.begin(), work_end = work_values->load_value_map.end(); + auto store_it = values->store_loc_set.begin(), store_end = values->store_loc_set.end(); + auto load_it = values->load_value_map.begin(), load_end = values->load_value_map.end(); + while (store_it != store_end || load_it != load_end) { + uint16_t loc; + if (store_it != store_end && (load_it == load_end || *store_it < load_it->first)) { + loc = *store_it; + ++store_it; + } else { + loc = load_it->first; + ++load_it; + DCHECK(store_it == store_end || cmp(loc, *store_it)); + } + while (work_it != work_end && cmp(work_it->first, loc)) { + work_it = work_values->load_value_map.erase(work_it); + } + if (work_it != work_end && !cmp(loc, work_it->first)) { + // The location matches, keep it. + ++work_it; + } + } + while (work_it != work_end) { + work_it = work_values->load_value_map.erase(work_it); + } +} + +void LocalValueNumbering::MergeEscapedRefs(const ValueNameSet::value_type& entry, + ValueNameSet::iterator hint) { + // See if the ref is either escaped or non-aliasing in each predecessor. + bool is_escaped = true; + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + if (lvn->non_aliasing_refs_.count(entry) == 0u && + lvn->escaped_refs_.count(entry) == 0u) { + is_escaped = false; + break; + } + } + if (is_escaped) { + escaped_refs_.emplace_hint(hint, entry); + } +} + +void LocalValueNumbering::MergeEscapedIFieldTypeClobberSets( + const EscapedIFieldClobberSet::value_type& entry, EscapedIFieldClobberSet::iterator hint) { + // Insert only type-clobber entries (field_id == kNoValue) of escaped refs. + if (entry.field_id == kNoValue && escaped_refs_.count(entry.base) != 0u) { + escaped_ifield_clobber_set_.emplace_hint(hint, entry); + } +} + +void LocalValueNumbering::MergeEscapedIFieldClobberSets( + const EscapedIFieldClobberSet::value_type& entry, EscapedIFieldClobberSet::iterator hint) { + // Insert only those entries of escaped refs that are not overridden by a type clobber. + if (!(hint == escaped_ifield_clobber_set_.end() && + hint->base == entry.base && hint->type == entry.type) && + escaped_refs_.count(entry.base) != 0u) { + escaped_ifield_clobber_set_.emplace_hint(hint, entry); + } +} + +void LocalValueNumbering::MergeEscapedArrayClobberSets( + const EscapedArrayClobberSet::value_type& entry, EscapedArrayClobberSet::iterator hint) { + if (escaped_refs_.count(entry.base) != 0u) { + escaped_array_clobber_set_.emplace_hint(hint, entry); + } +} + +void LocalValueNumbering::MergeNullChecked(const ValueNameSet::value_type& entry, + ValueNameSet::iterator hint) { + // Merge null_checked_ for this ref. + merge_names_.clear(); + merge_names_.resize(gvn_->merge_lvns_.size(), entry); + if (gvn_->NullCheckedInAllPredecessors(merge_names_)) { + null_checked_.insert(hint, entry); + } +} + +void LocalValueNumbering::MergeSFieldValues(const SFieldToValueMap::value_type& entry, + SFieldToValueMap::iterator hint) { + uint16_t field_id = entry.first; + merge_names_.clear(); + uint16_t value_name = kNoValue; + bool same_values = true; + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + // Get the value name as in HandleSGet() but don't modify *lvn. + auto it = lvn->sfield_value_map_.find(field_id); + if (it != lvn->sfield_value_map_.end()) { + value_name = it->second; + } else { + uint16_t type = gvn_->GetFieldType(field_id); + value_name = gvn_->LookupValue(kResolvedSFieldOp, field_id, + lvn->unresolved_sfield_version_[type], + lvn->global_memory_version_); + } + + same_values = same_values && (merge_names_.empty() || value_name == merge_names_.back()); + merge_names_.push_back(value_name); + } + if (same_values) { + // value_name already contains the result. + } else { + auto lb = merge_map_.lower_bound(merge_names_); + if (lb != merge_map_.end() && !merge_map_.key_comp()(merge_names_, lb->first)) { + value_name = lb->second; + } else { + value_name = gvn_->LookupValue(kMergeBlockSFieldVersionBumpOp, field_id, id_, kNoValue); + merge_map_.PutBefore(lb, merge_names_, value_name); + if (gvn_->NullCheckedInAllPredecessors(merge_names_)) { + null_checked_.insert(value_name); + } + } + } + sfield_value_map_.PutBefore(hint, field_id, value_name); +} + +void LocalValueNumbering::MergeNonAliasingIFieldValues(const IFieldLocToValueMap::value_type& entry, + IFieldLocToValueMap::iterator hint) { + uint16_t field_loc = entry.first; + merge_names_.clear(); + uint16_t value_name = kNoValue; + bool same_values = true; + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + // Get the value name as in HandleIGet() but don't modify *lvn. + auto it = lvn->non_aliasing_ifield_value_map_.find(field_loc); + if (it != lvn->non_aliasing_ifield_value_map_.end()) { + value_name = it->second; + } else { + value_name = gvn_->LookupValue(kNonAliasingIFieldInitialOp, field_loc, kNoValue, kNoValue); + } + + same_values = same_values && (merge_names_.empty() || value_name == merge_names_.back()); + merge_names_.push_back(value_name); + } + if (same_values) { + // value_name already contains the result. + } else { + auto lb = merge_map_.lower_bound(merge_names_); + if (lb != merge_map_.end() && !merge_map_.key_comp()(merge_names_, lb->first)) { + value_name = lb->second; + } else { + value_name = gvn_->LookupValue(kMergeBlockNonAliasingIFieldVersionBumpOp, field_loc, + id_, kNoValue); + merge_map_.PutBefore(lb, merge_names_, value_name); + if (gvn_->NullCheckedInAllPredecessors(merge_names_)) { + null_checked_.insert(value_name); + } + } + } + non_aliasing_ifield_value_map_.PutBefore(hint, field_loc, value_name); +} + +template +void LocalValueNumbering::MergeAliasingValues(const typename Map::value_type& entry, + typename Map::iterator hint) { + const typename Map::key_type& key = entry.first; + + auto it = (this->*map_ptr).PutBefore(hint, key, AliasingValues(this)); + AliasingValues* my_values = &it->second; + + const AliasingValues* cmp_values = nullptr; + bool same_version = !Versions::HasNewBaseVersion(gvn_, this, key); + uint16_t load_memory_version_for_same_version = kNoValue; + if (same_version) { + // Find the first non-null values. + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + auto it = (lvn->*map_ptr).find(key); + if (it != (lvn->*map_ptr).end()) { + cmp_values = &it->second; + break; + } + } + DCHECK(cmp_values != nullptr); // There must be at least one non-null values. + + // Check if we have identical memory versions, i.e. the global memory version, unresolved + // field version and the values' memory_version_before_stores, last_stored_value + // and store_loc_set are identical. + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + auto it = (lvn->*map_ptr).find(key); + if (it == (lvn->*map_ptr).end()) { + if (cmp_values->memory_version_before_stores != kNoValue) { + same_version = false; + break; + } + } else if (cmp_values->last_stored_value != it->second.last_stored_value || + cmp_values->memory_version_before_stores != it->second.memory_version_before_stores || + cmp_values->store_loc_set != it->second.store_loc_set) { + same_version = false; + break; + } else if (it->second.last_load_memory_version != kNoValue) { + DCHECK(load_memory_version_for_same_version == kNoValue || + load_memory_version_for_same_version == it->second.last_load_memory_version); + load_memory_version_for_same_version = it->second.last_load_memory_version; + } + } + } + + if (same_version) { + // Copy the identical values. + my_values->memory_version_before_stores = cmp_values->memory_version_before_stores; + my_values->last_stored_value = cmp_values->last_stored_value; + my_values->store_loc_set = cmp_values->store_loc_set; + my_values->last_load_memory_version = load_memory_version_for_same_version; + // Merge load values seen in all incoming arcs (i.e. an intersection). + if (!cmp_values->load_value_map.empty()) { + my_values->load_value_map = cmp_values->load_value_map; + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + auto it = (lvn->*map_ptr).find(key); + if (it == (lvn->*map_ptr).end() || it->second.load_value_map.empty()) { + my_values->load_value_map.clear(); + break; + } + InPlaceIntersectMaps(&my_values->load_value_map, it->second.load_value_map); + if (my_values->load_value_map.empty()) { + break; + } + } + } + } else { + // Bump version number for the merge. + my_values->memory_version_before_stores = my_values->last_load_memory_version = + Versions::LookupMergeBlockValue(gvn_, id_, key); + + // Calculate the locations that have been either read from or written to in each incoming LVN. + bool first_lvn = true; + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + auto it = (lvn->*map_ptr).find(key); + if (it == (lvn->*map_ptr).end()) { + my_values->load_value_map.clear(); + break; + } + if (first_lvn) { + first_lvn = false; + // Copy the first LVN's locations. Values will be overwritten later. + my_values->load_value_map = it->second.load_value_map; + for (uint16_t location : it->second.store_loc_set) { + my_values->load_value_map.Put(location, 0u); + } + } else { + IntersectAliasingValueLocations(my_values, &it->second); + } + } + // Calculate merged values for the intersection. + for (auto& load_value_entry : my_values->load_value_map) { + uint16_t location = load_value_entry.first; + bool same_values = true; + uint16_t value_name = kNoValue; + merge_names_.clear(); + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + value_name = Versions::LookupMergeValue(gvn_, lvn, key, location); + same_values = same_values && (merge_names_.empty() || value_name == merge_names_.back()); + merge_names_.push_back(value_name); + } + if (same_values) { + // value_name already contains the result. + } else { + auto lb = merge_map_.lower_bound(merge_names_); + if (lb != merge_map_.end() && !merge_map_.key_comp()(merge_names_, lb->first)) { + value_name = lb->second; + } else { + // NOTE: In addition to the key and id_ which don't change on an LVN recalculation + // during GVN, we also add location which can actually change on recalculation, so the + // value_name below may change. This could lead to an infinite loop if the location + // value name always changed when the refereced value name changes. However, given that + // we assign unique value names for other merges, such as Phis, such a dependency is + // not possible in a well-formed SSA graph. + value_name = Versions::LookupMergeLocationValue(gvn_, id_, key, location); + merge_map_.PutBefore(lb, merge_names_, value_name); + if (gvn_->NullCheckedInAllPredecessors(merge_names_)) { + null_checked_.insert(value_name); + } + } + } + load_value_entry.second = value_name; + } + } +} + +void LocalValueNumbering::Merge(MergeType merge_type) { + DCHECK_GE(gvn_->merge_lvns_.size(), 2u); + + IntersectSregValueMaps<&LocalValueNumbering::sreg_value_map_>(); + IntersectSregValueMaps<&LocalValueNumbering::sreg_wide_value_map_>(); + if (merge_type == kReturnMerge) { + // RETURN or PHI+RETURN. We need only sreg value maps. + return; + } + + MergeMemoryVersions(merge_type == kCatchMerge); + + // Merge non-aliasing maps/sets. + IntersectSets(); + if (!non_aliasing_refs_.empty() && merge_type == kCatchMerge) { + PruneNonAliasingRefsForCatch(); + } + if (!non_aliasing_refs_.empty()) { + MergeSets(); + MergeSets>(); + } + + // We won't do anything complicated for range checks, just calculate the intersection. + IntersectSets(); + + // Merge null_checked_. We may later insert more, such as merged object field values. + MergeSets(); + + if (merge_type == kCatchMerge) { + // Memory is clobbered. New memory version already created, don't merge aliasing locations. + return; + } + + DCHECK(merge_type == kNormalMerge); + + // Merge escaped refs and clobber sets. + MergeSets(); + if (!escaped_refs_.empty()) { + MergeSets(); + MergeSets(); + MergeSets(); + } + + MergeSets(); + MergeSets>(); + MergeSets>(); +} + +uint16_t LocalValueNumbering::MarkNonAliasingNonNull(MIR* mir) { + uint16_t res = GetOperandValue(mir->ssa_rep->defs[0]); + DCHECK(null_checked_.find(res) == null_checked_.end()); + null_checked_.insert(res); + non_aliasing_refs_.insert(res); + return res; +} + +bool LocalValueNumbering::IsNonAliasing(uint16_t reg) const { + return non_aliasing_refs_.find(reg) != non_aliasing_refs_.end(); +} + +bool LocalValueNumbering::IsNonAliasingIField(uint16_t reg, uint16_t field_id, + uint16_t type) const { + if (IsNonAliasing(reg)) { + return true; + } + if (escaped_refs_.find(reg) == escaped_refs_.end()) { + return false; + } + // Check for IPUTs to unresolved fields. + EscapedIFieldClobberKey key1 = { reg, type, kNoValue }; + if (escaped_ifield_clobber_set_.find(key1) != escaped_ifield_clobber_set_.end()) { + return false; + } + // Check for aliased IPUTs to the same field. + EscapedIFieldClobberKey key2 = { reg, type, field_id }; + return escaped_ifield_clobber_set_.find(key2) == escaped_ifield_clobber_set_.end(); +} + +bool LocalValueNumbering::IsNonAliasingArray(uint16_t reg, uint16_t type) const { + if (IsNonAliasing(reg)) { + return true; + } + if (escaped_refs_.count(reg) == 0u) { + return false; + } + // Check for aliased APUTs. + EscapedArrayClobberKey key = { reg, type }; + return escaped_array_clobber_set_.find(key) == escaped_array_clobber_set_.end(); +} + +void LocalValueNumbering::HandleNullCheck(MIR* mir, uint16_t reg) { + auto lb = null_checked_.lower_bound(reg); + if (lb != null_checked_.end() && *lb == reg) { + if (LIKELY(gvn_->CanModify())) { + if (gvn_->GetCompilationUnit()->verbose) { + LOG(INFO) << "Removing null check for 0x" << std::hex << mir->offset; + } + mir->optimization_flags |= MIR_IGNORE_NULL_CHECK; + } + } else { + null_checked_.insert(lb, reg); + } +} + +void LocalValueNumbering::HandleRangeCheck(MIR* mir, uint16_t array, uint16_t index) { + RangeCheckKey key = { array, index }; + auto lb = range_checked_.lower_bound(key); + if (lb != range_checked_.end() && !RangeCheckKeyComparator()(key, *lb)) { + if (LIKELY(gvn_->CanModify())) { + if (gvn_->GetCompilationUnit()->verbose) { + LOG(INFO) << "Removing range check for 0x" << std::hex << mir->offset; + } + mir->optimization_flags |= MIR_IGNORE_RANGE_CHECK; + } + } else { + // Mark range check completed. + range_checked_.insert(lb, key); + } +} + +void LocalValueNumbering::HandlePutObject(MIR* mir) { + // If we're storing a non-aliasing reference, stop tracking it as non-aliasing now. + uint16_t base = GetOperandValue(mir->ssa_rep->uses[0]); + HandleEscapingRef(base); +} + +void LocalValueNumbering::HandleEscapingRef(uint16_t base) { + auto it = non_aliasing_refs_.find(base); + if (it != non_aliasing_refs_.end()) { + non_aliasing_refs_.erase(it); + escaped_refs_.insert(base); + } +} + +uint16_t LocalValueNumbering::HandlePhi(MIR* mir) { + if (gvn_->merge_lvns_.empty()) { + // Running LVN without a full GVN? + return kNoValue; + } + int16_t num_uses = mir->ssa_rep->num_uses; + int32_t* uses = mir->ssa_rep->uses; + // Try to find out if this is merging wide regs. + if (mir->ssa_rep->defs[0] != 0 && + sreg_wide_value_map_.count(mir->ssa_rep->defs[0] - 1) != 0u) { + // This is the high part of a wide reg. Ignore the Phi. + return kNoValue; + } + bool wide = false; + for (int16_t i = 0; i != num_uses; ++i) { + if (sreg_wide_value_map_.count(uses[i]) != 0u) { + wide = true; + break; + } + } + // Iterate over *merge_lvns_ and skip incoming sregs for BBs without associated LVN. + uint16_t value_name = kNoValue; + merge_names_.clear(); + BasicBlockId* incoming = mir->meta.phi_incoming; + int16_t pos = 0; + bool same_values = true; + for (const LocalValueNumbering* lvn : gvn_->merge_lvns_) { + DCHECK_LT(pos, mir->ssa_rep->num_uses); + while (incoming[pos] != lvn->Id()) { + ++pos; + DCHECK_LT(pos, mir->ssa_rep->num_uses); + } + int s_reg = uses[pos]; + ++pos; + value_name = wide ? lvn->GetOperandValueWide(s_reg) : lvn->GetOperandValue(s_reg); + + same_values = same_values && (merge_names_.empty() || value_name == merge_names_.back()); + merge_names_.push_back(value_name); + } + if (same_values) { + // value_name already contains the result. + } else { + auto lb = merge_map_.lower_bound(merge_names_); + if (lb != merge_map_.end() && !merge_map_.key_comp()(merge_names_, lb->first)) { + value_name = lb->second; + } else { + value_name = gvn_->LookupValue(kNoValue, mir->ssa_rep->defs[0], kNoValue, kNoValue); + merge_map_.PutBefore(lb, merge_names_, value_name); + if (!wide && gvn_->NullCheckedInAllPredecessors(merge_names_)) { + null_checked_.insert(value_name); + } + } + } + if (wide) { + SetOperandValueWide(mir->ssa_rep->defs[0], value_name); + } else { + SetOperandValue(mir->ssa_rep->defs[0], value_name); + } + return value_name; +} + +uint16_t LocalValueNumbering::HandleAGet(MIR* mir, uint16_t opcode) { + // uint16_t type = opcode - Instruction::AGET; + uint16_t array = GetOperandValue(mir->ssa_rep->uses[0]); + HandleNullCheck(mir, array); + uint16_t index = GetOperandValue(mir->ssa_rep->uses[1]); + HandleRangeCheck(mir, array, index); + uint16_t type = opcode - Instruction::AGET; + // Establish value number for loaded register. + uint16_t res; + if (IsNonAliasingArray(array, type)) { + res = HandleAliasingValuesGet(&non_aliasing_array_value_map_, + array, index); + } else { + uint16_t location = gvn_->GetArrayLocation(array, index); + res = HandleAliasingValuesGet(&aliasing_array_value_map_, + type, location); + } + if (opcode == Instruction::AGET_WIDE) { + SetOperandValueWide(mir->ssa_rep->defs[0], res); + } else { + SetOperandValue(mir->ssa_rep->defs[0], res); + } + return res; +} + +void LocalValueNumbering::HandleAPut(MIR* mir, uint16_t opcode) { + 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]); + HandleNullCheck(mir, array); + uint16_t index = GetOperandValue(mir->ssa_rep->uses[index_idx]); + HandleRangeCheck(mir, array, index); + + uint16_t type = opcode - Instruction::APUT; + uint16_t value = (opcode == Instruction::APUT_WIDE) + ? GetOperandValueWide(mir->ssa_rep->uses[0]) + : GetOperandValue(mir->ssa_rep->uses[0]); + if (IsNonAliasing(array)) { + bool put_is_live = HandleAliasingValuesPut( + &non_aliasing_array_value_map_, array, index, value); + if (!put_is_live) { + // This APUT can be eliminated, it stores the same value that's already in the field. + // TODO: Eliminate the APUT. + return; + } + } else { + uint16_t location = gvn_->GetArrayLocation(array, index); + bool put_is_live = HandleAliasingValuesPut( + &aliasing_array_value_map_, type, location, value); + if (!put_is_live) { + // This APUT can be eliminated, it stores the same value that's already in the field. + // TODO: Eliminate the APUT. + return; + } + + // Clobber all escaped array refs for this type. + for (uint16_t escaped_array : escaped_refs_) { + EscapedArrayClobberKey clobber_key = { escaped_array, type }; + escaped_array_clobber_set_.insert(clobber_key); + } + } +} + +uint16_t LocalValueNumbering::HandleIGet(MIR* mir, uint16_t opcode) { + uint16_t base = GetOperandValue(mir->ssa_rep->uses[0]); + HandleNullCheck(mir, base); + const MirFieldInfo& field_info = gvn_->GetMirGraph()->GetIFieldLoweringInfo(mir); + uint16_t res; + if (!field_info.IsResolved() || field_info.IsVolatile()) { + // Volatile fields always get a new memory version; field id is irrelevant. + // Unresolved fields may be volatile, so handle them as such to be safe. + // Use result s_reg - will be unique. + res = gvn_->LookupValue(kNoValue, mir->ssa_rep->defs[0], kNoValue, kNoValue); + } else { + uint16_t type = opcode - Instruction::IGET; + uint16_t field_id = gvn_->GetFieldId(field_info, type); + if (IsNonAliasingIField(base, field_id, type)) { + uint16_t loc = gvn_->LookupValue(kNonAliasingIFieldLocOp, base, field_id, type); + auto lb = non_aliasing_ifield_value_map_.lower_bound(loc); + if (lb != non_aliasing_ifield_value_map_.end() && lb->first == loc) { + res = lb->second; + } else { + res = gvn_->LookupValue(kNonAliasingIFieldInitialOp, loc, kNoValue, kNoValue); + non_aliasing_ifield_value_map_.PutBefore(lb, loc, res); + } + } else { + res = HandleAliasingValuesGet(&aliasing_ifield_value_map_, + field_id, base); + } + } + if (opcode == Instruction::IGET_WIDE) { + SetOperandValueWide(mir->ssa_rep->defs[0], res); + } else { + SetOperandValue(mir->ssa_rep->defs[0], res); + } + return res; +} + +void LocalValueNumbering::HandleIPut(MIR* mir, uint16_t opcode) { + uint16_t type = opcode - Instruction::IPUT; + int base_reg = (opcode == Instruction::IPUT_WIDE) ? 2 : 1; + uint16_t base = GetOperandValue(mir->ssa_rep->uses[base_reg]); + HandleNullCheck(mir, base); + const MirFieldInfo& field_info = gvn_->GetMirGraph()->GetIFieldLoweringInfo(mir); + if (!field_info.IsResolved()) { + // Unresolved fields always alias with everything of the same type. + // Use mir->offset as modifier; without elaborate inlining, it will be unique. + unresolved_ifield_version_[type] = + gvn_->LookupValue(kUnresolvedIFieldOp, kNoValue, kNoValue, mir->offset); + + // For simplicity, treat base as escaped now. + HandleEscapingRef(base); + + // Clobber all fields of escaped references of the same type. + for (uint16_t escaped_ref : escaped_refs_) { + EscapedIFieldClobberKey clobber_key = { escaped_ref, type, kNoValue }; + escaped_ifield_clobber_set_.insert(clobber_key); + } + + // Aliasing fields of the same type may have been overwritten. + auto it = aliasing_ifield_value_map_.begin(), end = aliasing_ifield_value_map_.end(); + while (it != end) { + if (gvn_->GetFieldType(it->first) != type) { + ++it; + } else { + it = aliasing_ifield_value_map_.erase(it); + } + } + } else if (field_info.IsVolatile()) { + // Nothing to do, resolved volatile fields always get a new memory version anyway and + // can't alias with resolved non-volatile fields. + } else { + uint16_t field_id = gvn_->GetFieldId(field_info, type); + uint16_t value = (opcode == Instruction::IPUT_WIDE) + ? GetOperandValueWide(mir->ssa_rep->uses[0]) + : GetOperandValue(mir->ssa_rep->uses[0]); + if (IsNonAliasing(base)) { + uint16_t loc = gvn_->LookupValue(kNonAliasingIFieldLocOp, base, field_id, type); + auto lb = non_aliasing_ifield_value_map_.lower_bound(loc); + if (lb != non_aliasing_ifield_value_map_.end() && lb->first == loc) { + if (lb->second == value) { + // This IPUT can be eliminated, it stores the same value that's already in the field. + // TODO: Eliminate the IPUT. + return; + } + lb->second = value; // Overwrite. + } else { + non_aliasing_ifield_value_map_.PutBefore(lb, loc, value); + } + } else { + bool put_is_live = HandleAliasingValuesPut( + &aliasing_ifield_value_map_, field_id, base, value); + if (!put_is_live) { + // This IPUT can be eliminated, it stores the same value that's already in the field. + // TODO: Eliminate the IPUT. + return; + } + + // Clobber all fields of escaped references for this field. + for (uint16_t escaped_ref : escaped_refs_) { + EscapedIFieldClobberKey clobber_key = { escaped_ref, type, field_id }; + escaped_ifield_clobber_set_.insert(clobber_key); + } + } + } +} + +uint16_t LocalValueNumbering::HandleSGet(MIR* mir, uint16_t opcode) { + const MirSFieldLoweringInfo& field_info = gvn_->GetMirGraph()->GetSFieldLoweringInfo(mir); + if (!field_info.IsInitialized() && (mir->optimization_flags & MIR_IGNORE_CLINIT_CHECK) == 0) { + // Class initialization can call arbitrary functions, we need to wipe aliasing values. + HandleInvokeOrClInit(mir); + } + uint16_t res; + if (!field_info.IsResolved() || field_info.IsVolatile()) { + // Volatile fields always get a new memory version; field id is irrelevant. + // Unresolved fields may be volatile, so handle them as such to be safe. + // Use result s_reg - will be unique. + res = gvn_->LookupValue(kNoValue, mir->ssa_rep->defs[0], kNoValue, kNoValue); + } else { + uint16_t type = opcode - Instruction::SGET; + uint16_t field_id = gvn_->GetFieldId(field_info, type); + auto lb = sfield_value_map_.lower_bound(field_id); + if (lb != sfield_value_map_.end() && lb->first == field_id) { + res = lb->second; + } else { + // Resolved non-volatile static fields can alias with non-resolved fields of the same type, + // so we need to use unresolved_sfield_version_[type] in addition to global_memory_version_ + // to determine the version of the field. + res = gvn_->LookupValue(kResolvedSFieldOp, field_id, + unresolved_sfield_version_[type], global_memory_version_); + sfield_value_map_.PutBefore(lb, field_id, res); + } + } + if (opcode == Instruction::SGET_WIDE) { + SetOperandValueWide(mir->ssa_rep->defs[0], res); + } else { + SetOperandValue(mir->ssa_rep->defs[0], res); + } + return res; +} + +void LocalValueNumbering::HandleSPut(MIR* mir, uint16_t opcode) { + const MirSFieldLoweringInfo& field_info = gvn_->GetMirGraph()->GetSFieldLoweringInfo(mir); + if (!field_info.IsInitialized() && (mir->optimization_flags & MIR_IGNORE_CLINIT_CHECK) == 0) { + // Class initialization can call arbitrary functions, we need to wipe aliasing values. + HandleInvokeOrClInit(mir); + } + uint16_t type = opcode - Instruction::SPUT; + if (!field_info.IsResolved()) { + // Unresolved fields always alias with everything of the same type. + // Use mir->offset as modifier; without elaborate inlining, it will be unique. + unresolved_sfield_version_[type] = + gvn_->LookupValue(kUnresolvedSFieldOp, kNoValue, kNoValue, mir->offset); + RemoveSFieldsForType(type); + } else if (field_info.IsVolatile()) { + // Nothing to do, resolved volatile fields always get a new memory version anyway and + // can't alias with resolved non-volatile fields. + } else { + uint16_t field_id = gvn_->GetFieldId(field_info, type); + uint16_t value = (opcode == Instruction::SPUT_WIDE) + ? GetOperandValueWide(mir->ssa_rep->uses[0]) + : GetOperandValue(mir->ssa_rep->uses[0]); + // Resolved non-volatile static fields can alias with non-resolved fields of the same type, + // so we need to use unresolved_sfield_version_[type] in addition to global_memory_version_ + // to determine the version of the field. + auto lb = sfield_value_map_.lower_bound(field_id); + if (lb != sfield_value_map_.end() && lb->first == field_id) { + if (lb->second == value) { + // This SPUT can be eliminated, it stores the same value that's already in the field. + // TODO: Eliminate the SPUT. + return; + } + lb->second = value; // Overwrite. + } else { + sfield_value_map_.PutBefore(lb, field_id, value); + } + } +} + +void LocalValueNumbering::RemoveSFieldsForType(uint16_t type) { + // Erase all static fields of this type from the sfield_value_map_. + for (auto it = sfield_value_map_.begin(), end = sfield_value_map_.end(); it != end; ) { + if (gvn_->GetFieldType(it->first) == type) { + it = sfield_value_map_.erase(it); + } else { + ++it; + } + } +} + +void LocalValueNumbering::HandleInvokeOrClInit(MIR* mir) { + // Use mir->offset as modifier; without elaborate inlining, it will be unique. + global_memory_version_ = + gvn_->LookupValue(kInvokeMemoryVersionBumpOp, 0u, 0u, mir->offset); + // All static fields and instance fields and array elements of aliasing references, + // including escaped references, may have been modified. + sfield_value_map_.clear(); + aliasing_ifield_value_map_.clear(); + aliasing_array_value_map_.clear(); + escaped_refs_.clear(); + escaped_ifield_clobber_set_.clear(); + escaped_array_clobber_set_.clear(); +} uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { - uint16_t res = NO_VALUE; + uint16_t res = kNoValue; uint16_t opcode = mir->dalvikInsn.opcode; switch (opcode) { case Instruction::NOP: @@ -28,16 +1351,12 @@ uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { 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: @@ -52,8 +1371,59 @@ uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { case Instruction::IF_GEZ: case Instruction::IF_GTZ: case Instruction::IF_LEZ: - case Instruction::INVOKE_STATIC_RANGE: - case Instruction::INVOKE_STATIC: + case kMirOpFusedCmplFloat: + case kMirOpFusedCmpgFloat: + case kMirOpFusedCmplDouble: + case kMirOpFusedCmpgDouble: + case kMirOpFusedCmpLong: + // Nothing defined - take no action. + break; + + case Instruction::MONITOR_ENTER: + HandleNullCheck(mir, GetOperandValue(mir->ssa_rep->uses[0])); + // NOTE: Keeping all aliasing values intact. Programs that rely on loads/stores of the + // same non-volatile locations outside and inside a synchronized block being different + // contain races that we cannot fix. + break; + + case Instruction::MONITOR_EXIT: + HandleNullCheck(mir, GetOperandValue(mir->ssa_rep->uses[0])); + // If we're running GVN and CanModify(), uneliminated null check indicates bytecode error. + if ((gvn_->GetCompilationUnit()->disable_opt & (1u << kGlobalValueNumbering)) == 0u && + gvn_->CanModify() && (mir->optimization_flags & MIR_IGNORE_NULL_CHECK) == 0) { + LOG(WARNING) << "Bytecode error: MONITOR_EXIT is still null checked at 0x" << std::hex + << mir->offset << " in " << PrettyMethod(gvn_->cu_->method_idx, *gvn_->cu_->dex_file); + } + break; + + case Instruction::FILLED_NEW_ARRAY: + case Instruction::FILLED_NEW_ARRAY_RANGE: + // Nothing defined but the result will be unique and non-null. + if (mir->next != nullptr && mir->next->dalvikInsn.opcode == Instruction::MOVE_RESULT_OBJECT) { + uint16_t array = MarkNonAliasingNonNull(mir->next); + // Do not SetOperandValue(), we'll do that when we process the MOVE_RESULT_OBJECT. + if (kLocalValueNumberingEnableFilledNewArrayTracking && mir->ssa_rep->num_uses != 0u) { + AliasingValues* values = GetAliasingValues(&non_aliasing_array_value_map_, array); + // Clear the value if we got a merged version in a loop. + *values = AliasingValues(this); + for (size_t i = 0u, count = mir->ssa_rep->num_uses; i != count; ++i) { + DCHECK_EQ(High16Bits(i), 0u); + uint16_t index = gvn_->LookupValue(Instruction::CONST, i, 0u, 0); + uint16_t value = GetOperandValue(mir->ssa_rep->uses[i]); + values->load_value_map.Put(index, value); + RangeCheckKey key = { array, index }; + range_checked_.insert(key); + } + } + // The MOVE_RESULT_OBJECT will be processed next and we'll return the value name then. + } + // All args escaped (if references). + for (size_t i = 0u, count = mir->ssa_rep->num_uses; i != count; ++i) { + uint16_t reg = GetOperandValue(mir->ssa_rep->uses[i]); + HandleEscapingRef(reg); + } + break; + case Instruction::INVOKE_DIRECT: case Instruction::INVOKE_DIRECT_RANGE: case Instruction::INVOKE_VIRTUAL: @@ -61,41 +1431,60 @@ uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { 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. + case Instruction::INVOKE_INTERFACE_RANGE: { + // Nothing defined but handle the null check. + uint16_t reg = GetOperandValue(mir->ssa_rep->uses[0]); + HandleNullCheck(mir, reg); + } + // Intentional fall-through. + case Instruction::INVOKE_STATIC: + case Instruction::INVOKE_STATIC_RANGE: + if ((mir->optimization_flags & MIR_INLINED) == 0) { + // Make ref args aliasing. + for (size_t i = 0u, count = mir->ssa_rep->num_uses; i != count; ++i) { + uint16_t reg = GetOperandValue(mir->ssa_rep->uses[i]); + non_aliasing_refs_.erase(reg); + } + HandleInvokeOrClInit(mir); + } break; - case Instruction::MOVE_EXCEPTION: case Instruction::MOVE_RESULT: case Instruction::MOVE_RESULT_OBJECT: case Instruction::INSTANCE_OF: + // 1 result, treat as unique each time, use result s_reg - will be unique. + res = GetOperandValue(mir->ssa_rep->defs[0]); + SetOperandValue(mir->ssa_rep->defs[0], res); + break; + case Instruction::MOVE_EXCEPTION: case Instruction::NEW_INSTANCE: + case Instruction::CONST_CLASS: + case Instruction::NEW_ARRAY: + // 1 result, treat as unique each time, use result s_reg - will be unique. + res = MarkNonAliasingNonNull(mir); + SetOperandValue(mir->ssa_rep->defs[0], res); + break; 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); - } + // These strings are internalized, so assign value based on the string pool index. + res = gvn_->LookupValue(Instruction::CONST_STRING, Low16Bits(mir->dalvikInsn.vB), + High16Bits(mir->dalvikInsn.vB), 0); + SetOperandValue(mir->ssa_rep->defs[0], res); + null_checked_.insert(res); // May already be there. + // NOTE: Hacking the contents of an internalized string via reflection is possible + // but the behavior is undefined. Therefore, we consider the string constant and + // the reference non-aliasing. + // TUNING: We could keep this property even if the reference "escapes". + non_aliasing_refs_.insert(res); // May already be there. 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); - } + case Instruction::MOVE_RESULT_WIDE: + // 1 wide result, treat as unique each time, use result s_reg - will be unique. + 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. - */ + res = HandlePhi(mir); break; case Instruction::MOVE: @@ -104,73 +1493,75 @@ uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { 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); - } + case kMirOpCopy: + // Just copy value number of source to value number of result. + 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); - } + case Instruction::MOVE_WIDE_FROM16: + // Just copy value number of source to value number of result. + 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); - } + case Instruction::CONST_16: + res = gvn_->LookupValue(Instruction::CONST, Low16Bits(mir->dalvikInsn.vB), + High16Bits(mir->dalvikInsn.vB), 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); - } + case Instruction::CONST_HIGH16: + res = gvn_->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 low_res = gvn_->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); + high_res = gvn_->LookupValue(Instruction::CONST, 0xffff, 0xffff, 2); } else { - high_res = LookupValue(Instruction::CONST, 0, 0, 2); + high_res = gvn_->LookupValue(Instruction::CONST, 0, 0, 2); } - uint16_t res = LookupValue(Instruction::CONST, low_res, high_res, 3); - SetOperandValue(mir->ssa_rep->defs[0], res); + res = gvn_->LookupValue(Instruction::CONST, low_res, high_res, 3); + SetOperandValueWide(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); + uint16_t low_res = gvn_->LookupValue(Instruction::CONST, Low16Bits(low_word), + High16Bits(low_word), 1); + uint16_t high_res = gvn_->LookupValue(Instruction::CONST, Low16Bits(high_word), + High16Bits(high_word), 2); + res = gvn_->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); + uint16_t low_res = gvn_->LookupValue(Instruction::CONST, 0, 0, 1); + uint16_t high_res = gvn_->LookupValue(Instruction::CONST, 0, + Low16Bits(mir->dalvikInsn.vB), 2); + res = gvn_->LookupValue(Instruction::CONST, low_res, high_res, 3); SetOperandValueWide(mir->ssa_rep->defs[0], res); } break; - case Instruction::ARRAY_LENGTH: + case Instruction::ARRAY_LENGTH: { + // Handle the null check. + uint16_t reg = GetOperandValue(mir->ssa_rep->uses[0]); + HandleNullCheck(mir, reg); + } + // Intentional fall-through. case Instruction::NEG_INT: case Instruction::NOT_INT: case Instruction::NEG_FLOAT: @@ -181,7 +1572,7 @@ uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { 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); + res = gvn_->LookupValue(opcode, operand1, kNoValue, kNoValue); SetOperandValue(mir->ssa_rep->defs[0], res); } break; @@ -191,8 +1582,8 @@ uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { 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); + uint16_t operand1 = GetOperandValueWide(mir->ssa_rep->uses[0]); + res = gvn_->LookupValue(opcode, operand1, kNoValue, kNoValue); SetOperandValue(mir->ssa_rep->defs[0], res); } break; @@ -205,7 +1596,7 @@ uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { 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); + res = gvn_->LookupValue(opcode, operand1, kNoValue, kNoValue); SetOperandValueWide(mir->ssa_rep->defs[0], res); } break; @@ -215,8 +1606,8 @@ uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { 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); + uint16_t operand1 = GetOperandValue(mir->ssa_rep->uses[0]); + res = gvn_->LookupValue(opcode, operand1, kNoValue, kNoValue); SetOperandValueWide(mir->ssa_rep->defs[0], res); } break; @@ -227,7 +1618,7 @@ uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { // 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); + res = gvn_->LookupValue(opcode, operand1, operand2, kNoValue); SetOperandValue(mir->ssa_rep->defs[0], res); } break; @@ -259,7 +1650,7 @@ uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { // 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); + res = gvn_->LookupValue(opcode, operand1, operand2, kNoValue); SetOperandValue(mir->ssa_rep->defs[0], res); } break; @@ -293,7 +1684,7 @@ uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { // 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); + res = gvn_->LookupValue(opcode, operand1, operand2, kNoValue); SetOperandValueWide(mir->ssa_rep->defs[0], res); } break; @@ -306,8 +1697,8 @@ uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { 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); + uint16_t operand2 = GetOperandValue(mir->ssa_rep->uses[2]); + res = gvn_->LookupValue(opcode, operand1, operand2, kNoValue); SetOperandValueWide(mir->ssa_rep->defs[0], res); } break; @@ -325,7 +1716,7 @@ uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { // 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); + res = gvn_->LookupValue(opcode, operand1, operand2, kNoValue); SetOperandValue(mir->ssa_rep->defs[0], res); } break; @@ -349,165 +1740,78 @@ uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) { 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 + // Same as res = op + 2 operands, except use vC 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); + uint16_t operand2 = gvn_->LookupValue(Instruction::CONST, mir->dalvikInsn.vC, 0, 0); + res = gvn_->LookupValue(opcode, operand1, operand2, kNoValue); SetOperandValue(mir->ssa_rep->defs[0], res); } break; - case Instruction::AGET_WIDE: - case Instruction::AGET: case Instruction::AGET_OBJECT: + case Instruction::AGET: + case Instruction::AGET_WIDE: 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); - } - } + case Instruction::AGET_SHORT: + res = HandleAGet(mir, opcode); break; - case Instruction::APUT_WIDE: - case Instruction::APUT: case Instruction::APUT_OBJECT: + HandlePutObject(mir); + // Intentional fall-through. + case Instruction::APUT: + case Instruction::APUT_WIDE: + case Instruction::APUT_BYTE: + case Instruction::APUT_BOOLEAN: 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); - } + HandleAPut(mir, opcode); break; case Instruction::IGET_OBJECT: - case Instruction::IGET_WIDE: case Instruction::IGET: + case Instruction::IGET_WIDE: + case Instruction::IGET_BOOLEAN: + case Instruction::IGET_BYTE: 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); - } - } + res = HandleIGet(mir, opcode); break; - case Instruction::IPUT_WIDE: case Instruction::IPUT_OBJECT: + HandlePutObject(mir); + // Intentional fall-through. case Instruction::IPUT: + case Instruction::IPUT_WIDE: 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); - } + case Instruction::IPUT_SHORT: + HandleIPut(mir, opcode); break; case Instruction::SGET_OBJECT: case Instruction::SGET: + case Instruction::SGET_WIDE: 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); - } - } + res = HandleSGet(mir, opcode); break; case Instruction::SPUT_OBJECT: + HandlePutObject(mir); + // Intentional fall-through. case Instruction::SPUT: + case Instruction::SPUT_WIDE: 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); - } + HandleSPut(mir, opcode); break; } return res; diff --git a/compiler/dex/local_value_numbering.h b/compiler/dex/local_value_numbering.h index 33ca8f1ad85..855d66de8b1 100644 --- a/compiler/dex/local_value_numbering.h +++ b/compiler/dex/local_value_numbering.h @@ -17,124 +17,380 @@ #ifndef ART_COMPILER_DEX_LOCAL_VALUE_NUMBERING_H_ #define ART_COMPILER_DEX_LOCAL_VALUE_NUMBERING_H_ -#include "compiler_internals.h" +#include -#define NO_VALUE 0xffff -#define ARRAY_REF 0xfffe +#include "compiler_internals.h" +#include "global_value_numbering.h" +#include "utils/scoped_arena_allocator.h" +#include "utils/scoped_arena_containers.h" 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 DexFile; + +// Enable/disable tracking values stored in the FILLED_NEW_ARRAY result. +static constexpr bool kLocalValueNumberingEnableFilledNewArrayTracking = true; class LocalValueNumbering { + private: + static constexpr uint16_t kNoValue = GlobalValueNumbering::kNoValue; + public: - explicit LocalValueNumbering(CompilationUnit* cu) : cu_(cu) {} + LocalValueNumbering(GlobalValueNumbering* gvn, BasicBlockId id, ScopedArenaAllocator* allocator); + + BasicBlockId Id() const { + return id_; + } - 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)); + bool Equals(const LocalValueNumbering& other) const; + + uint16_t GetSRegValueName(uint16_t s_reg) const { + return GetOperandValue(s_reg); + } + + void SetValueNameNullChecked(uint16_t value_name) { + null_checked_.insert(value_name); + } + + bool IsValueNullChecked(uint16_t value_name) const { + return null_checked_.find(value_name) != null_checked_.end(); + } + + bool IsSregValue(uint16_t s_reg, uint16_t value_name) const { + auto it = sreg_value_map_.find(s_reg); + if (it != sreg_value_map_.end()) { + return it->second == value_name; + } else { + return gvn_->HasValue(kNoValue, s_reg, kNoValue, kNoValue, value_name); + } + } + + enum MergeType { + kNormalMerge, + kCatchMerge, + kReturnMerge, // RETURN or PHI+RETURN. Merge only sreg maps. }; - 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; + void MergeOne(const LocalValueNumbering& other, MergeType merge_type); + void Merge(MergeType merge_type); // Merge gvn_->merge_lvns_. + + uint16_t GetValueNumber(MIR* mir); + + // LocalValueNumbering should be allocated on the ArenaStack (or the native stack). + static void* operator new(size_t size, ScopedArenaAllocator* allocator) { + return allocator->Alloc(sizeof(LocalValueNumbering), kArenaAllocMisc); + } + + // Allow delete-expression to destroy a LocalValueNumbering object without deallocation. + static void operator delete(void* ptr) { UNUSED(ptr); } + + private: + // A set of value names. + typedef GlobalValueNumbering::ValueNameSet ValueNameSet; + + // Field types correspond to the ordering of GET/PUT instructions; this order is the same + // for IGET, IPUT, SGET, SPUT, AGET and APUT: + // op 0 + // op_WIDE 1 + // op_OBJECT 2 + // op_BOOLEAN 3 + // op_BYTE 4 + // op_CHAR 5 + // op_SHORT 6 + static constexpr size_t kFieldTypeCount = 7; + + // Key is s_reg, value is value name. + typedef ScopedArenaSafeMap SregValueMap; + + void SetOperandValueImpl(uint16_t s_reg, uint16_t value, SregValueMap* map) { + DCHECK_EQ(map->count(s_reg), 0u) << PrettyMethod(gvn_->cu_->method_idx, *gvn_->cu_->dex_file) + << " LVN id: " << id_ << ", s_reg: " << s_reg; + map->Put(s_reg, value); + } + + uint16_t GetOperandValueImpl(int s_reg, const SregValueMap* map) const { + uint16_t res = kNoValue; + auto lb = map->find(s_reg); + if (lb != map->end()) { + res = lb->second; } else { - res = value_map_.size() + 1; - value_map_.Put(key, res); + // Using the original value; s_reg refers to an input reg. + res = gvn_->LookupValue(kNoValue, s_reg, kNoValue, kNoValue); } return res; + } + + void SetOperandValue(uint16_t s_reg, uint16_t value) { + SetOperandValueImpl(s_reg, value, &sreg_value_map_); }; - 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 GetOperandValue(int s_reg) const { + return GetOperandValueImpl(s_reg, &sreg_value_map_); }; - 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; + void SetOperandValueWide(uint16_t s_reg, uint16_t value) { + SetOperandValueImpl(s_reg, value, &sreg_wide_value_map_); + }; + + uint16_t GetOperandValueWide(int s_reg) const { + return GetOperandValueImpl(s_reg, &sreg_wide_value_map_); + }; + + struct RangeCheckKey { + uint16_t array; + uint16_t index; + + // NOTE: Can't define this at namespace scope for a private struct. + bool operator==(const RangeCheckKey& other) const { + return array == other.array && index == other.index; } - 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++; + struct RangeCheckKeyComparator { + bool operator()(const RangeCheckKey& lhs, const RangeCheckKey& rhs) const { + if (lhs.array != rhs.array) { + return lhs.array < rhs.array; + } + return lhs.index < rhs.index; } }; - 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); + typedef ScopedArenaSet RangeCheckSet; + + // Maps instance field "location" (derived from base, field_id and type) to value name. + typedef ScopedArenaSafeMap IFieldLocToValueMap; + + // Maps static field id to value name + typedef ScopedArenaSafeMap SFieldToValueMap; + + struct EscapedIFieldClobberKey { + uint16_t base; // Or array. + uint16_t type; + uint16_t field_id; // None (kNoValue) for arrays and unresolved instance field stores. + + // NOTE: Can't define this at namespace scope for a private struct. + bool operator==(const EscapedIFieldClobberKey& other) const { + return base == other.base && type == other.type && field_id == other.field_id; } }; - 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); + struct EscapedIFieldClobberKeyComparator { + bool operator()(const EscapedIFieldClobberKey& lhs, const EscapedIFieldClobberKey& rhs) const { + // Compare base first. This makes sequential iteration respect the order of base. + if (lhs.base != rhs.base) { + return lhs.base < rhs.base; + } + // Compare type second. This makes the type-clobber entries (field_id == kNoValue) last + // for given base and type and makes it easy to prune unnecessary entries when merging + // escaped_ifield_clobber_set_ from multiple LVNs. + if (lhs.type != rhs.type) { + return lhs.type < rhs.type; + } + return lhs.field_id < rhs.field_id; } - 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); + typedef ScopedArenaSet + EscapedIFieldClobberSet; + + struct EscapedArrayClobberKey { + uint16_t base; + uint16_t type; + + // NOTE: Can't define this at namespace scope for a private struct. + bool operator==(const EscapedArrayClobberKey& other) const { + return base == other.base && type == other.type; } }; - 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); + struct EscapedArrayClobberKeyComparator { + bool operator()(const EscapedArrayClobberKey& lhs, const EscapedArrayClobberKey& rhs) const { + // Compare base first. This makes sequential iteration respect the order of base. + if (lhs.base != rhs.base) { + return lhs.base < rhs.base; + } + return lhs.type < rhs.type; } - return res; }; - uint16_t GetValueNumber(MIR* mir); + // Clobber set for previously non-aliasing array refs that escaped. + typedef ScopedArenaSet + EscapedArrayClobberSet; + + // Known location values for an aliasing set. The set can be tied to one of: + // 1. Instance field. The locations are aliasing references used to access the field. + // 2. Non-aliasing array reference. The locations are indexes to the array. + // 3. Aliasing array type. The locations are (reference, index) pair ids assigned by GVN. + // In each case we keep track of the last stored value, if any, and the set of locations + // where it was stored. We also keep track of all values known for the current write state + // (load_value_map), which can be known either because they have been loaded since the last + // store or because they contained the last_stored_value before the store and thus could not + // have changed as a result. + struct AliasingValues { + explicit AliasingValues(LocalValueNumbering* lvn) + : memory_version_before_stores(kNoValue), + last_stored_value(kNoValue), + store_loc_set(std::less(), lvn->null_checked_.get_allocator()), + last_load_memory_version(kNoValue), + load_value_map(std::less(), lvn->null_checked_.get_allocator()) { + } + + uint16_t memory_version_before_stores; // kNoValue if start version for the field. + uint16_t last_stored_value; // Last stored value name, kNoValue if none. + ValueNameSet store_loc_set; // Where was last_stored_value stored. + + // Maps refs (other than stored_to) to currently known values for this field other. On write, + // anything that differs from the written value is removed as it may be overwritten. + uint16_t last_load_memory_version; // kNoValue if not known. + ScopedArenaSafeMap load_value_map; + + // NOTE: Can't define this at namespace scope for a private struct. + bool operator==(const AliasingValues& other) const { + return memory_version_before_stores == other.memory_version_before_stores && + last_load_memory_version == other.last_load_memory_version && + last_stored_value == other.last_stored_value && + store_loc_set == other.store_loc_set && + load_value_map == other.load_value_map; + } + }; + + // Maps instance field id to AliasingValues, locations are object refs. + typedef ScopedArenaSafeMap AliasingIFieldValuesMap; + + // Maps non-aliasing array reference to AliasingValues, locations are array indexes. + typedef ScopedArenaSafeMap NonAliasingArrayValuesMap; + + // Maps aliasing array type to AliasingValues, locations are (array, index) pair ids. + typedef ScopedArenaSafeMap AliasingArrayValuesMap; + + // Helper classes defining versions for updating and merging the AliasingValues maps above. + class AliasingIFieldVersions; + class NonAliasingArrayVersions; + class AliasingArrayVersions; + + template + AliasingValues* GetAliasingValues(Map* map, const typename Map::key_type& key); + + template + void UpdateAliasingValuesLoadVersion(const KeyType& key, AliasingValues* values); + + template + static uint16_t AliasingValuesMergeGet(GlobalValueNumbering* gvn, + const LocalValueNumbering* lvn, + Map* map, const typename Map::key_type& key, + uint16_t location); + + template + uint16_t HandleAliasingValuesGet(Map* map, const typename Map::key_type& key, + uint16_t location); + + template + bool HandleAliasingValuesPut(Map* map, const typename Map::key_type& key, + uint16_t location, uint16_t value); + + template + void CopyAliasingValuesMap(ScopedArenaSafeMap* dest, + const ScopedArenaSafeMap& src); + + uint16_t MarkNonAliasingNonNull(MIR* mir); + bool IsNonAliasing(uint16_t reg) const; + bool IsNonAliasingIField(uint16_t reg, uint16_t field_id, uint16_t type) const; + bool IsNonAliasingArray(uint16_t reg, uint16_t type) const; + void HandleNullCheck(MIR* mir, uint16_t reg); + void HandleRangeCheck(MIR* mir, uint16_t array, uint16_t index); + void HandlePutObject(MIR* mir); + void HandleEscapingRef(uint16_t base); + uint16_t HandlePhi(MIR* mir); + uint16_t HandleAGet(MIR* mir, uint16_t opcode); + void HandleAPut(MIR* mir, uint16_t opcode); + uint16_t HandleIGet(MIR* mir, uint16_t opcode); + void HandleIPut(MIR* mir, uint16_t opcode); + uint16_t HandleSGet(MIR* mir, uint16_t opcode); + void HandleSPut(MIR* mir, uint16_t opcode); + void RemoveSFieldsForType(uint16_t type); + void HandleInvokeOrClInit(MIR* mir); + + bool SameMemoryVersion(const LocalValueNumbering& other) const; + + uint16_t NewMemoryVersion(uint16_t* new_version); + void MergeMemoryVersions(bool clobbered_catch); + + void PruneNonAliasingRefsForCatch(); + + template + void IntersectSets(); + + void CopyLiveSregValues(SregValueMap* dest, const SregValueMap& src); + + // Intersect maps as sets. The value type must be equality-comparable. + template + void IntersectSregValueMaps(); + + // Intersect maps as sets. The value type must be equality-comparable. + template + static void InPlaceIntersectMaps(Map* work_map, const Map& other_map); + + template + void MergeSets(); + + void IntersectAliasingValueLocations(AliasingValues* work_values, const AliasingValues* values); + + void MergeEscapedRefs(const ValueNameSet::value_type& entry, ValueNameSet::iterator hint); + void MergeEscapedIFieldTypeClobberSets(const EscapedIFieldClobberSet::value_type& entry, + EscapedIFieldClobberSet::iterator hint); + void MergeEscapedIFieldClobberSets(const EscapedIFieldClobberSet::value_type& entry, + EscapedIFieldClobberSet::iterator hint); + void MergeEscapedArrayClobberSets(const EscapedArrayClobberSet::value_type& entry, + EscapedArrayClobberSet::iterator hint); + void MergeNullChecked(const ValueNameSet::value_type& entry, ValueNameSet::iterator hint); + void MergeSFieldValues(const SFieldToValueMap::value_type& entry, + SFieldToValueMap::iterator hint); + void MergeNonAliasingIFieldValues(const IFieldLocToValueMap::value_type& entry, + IFieldLocToValueMap::iterator hint); + + template + void MergeAliasingValues(const typename Map::value_type& entry, typename Map::iterator hint); + + GlobalValueNumbering* gvn_; + + // We're using the block id as a 16-bit operand value for some lookups. + COMPILE_ASSERT(sizeof(BasicBlockId) == sizeof(uint16_t), BasicBlockId_must_be_16_bit); + BasicBlockId id_; - private: - CompilationUnit* const cu_; SregValueMap sreg_value_map_; SregValueMap sreg_wide_value_map_; - ValueMap value_map_; - MemoryVersionMap memory_version_map_; - std::set null_checked_; + + SFieldToValueMap sfield_value_map_; + IFieldLocToValueMap non_aliasing_ifield_value_map_; + AliasingIFieldValuesMap aliasing_ifield_value_map_; + NonAliasingArrayValuesMap non_aliasing_array_value_map_; + AliasingArrayValuesMap aliasing_array_value_map_; + + // Data for dealing with memory clobbering and store/load aliasing. + uint16_t global_memory_version_; + uint16_t unresolved_sfield_version_[kFieldTypeCount]; + uint16_t unresolved_ifield_version_[kFieldTypeCount]; + // Value names of references to objects that cannot be reached through a different value name. + ValueNameSet non_aliasing_refs_; + // Previously non-aliasing refs that escaped but can still be used for non-aliasing AGET/IGET. + ValueNameSet escaped_refs_; + // Blacklists for cases where escaped_refs_ can't be used. + EscapedIFieldClobberSet escaped_ifield_clobber_set_; + EscapedArrayClobberSet escaped_array_clobber_set_; + + // Range check and null check elimination. + RangeCheckSet range_checked_; + ValueNameSet null_checked_; + + // Reuse one vector for all merges to avoid leaking too much memory on the ArenaStack. + ScopedArenaVector merge_names_; + // Map to identify when different locations merge the same values. + ScopedArenaSafeMap, uint16_t> merge_map_; + // New memory version for merge, kNoValue if all memory versions matched. + uint16_t merge_new_memory_version_; + + DISALLOW_COPY_AND_ASSIGN(LocalValueNumbering); }; } // namespace art diff --git a/compiler/dex/local_value_numbering_test.cc b/compiler/dex/local_value_numbering_test.cc new file mode 100644 index 00000000000..e4e944e8a8c --- /dev/null +++ b/compiler/dex/local_value_numbering_test.cc @@ -0,0 +1,732 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compiler_internals.h" +#include "global_value_numbering.h" +#include "local_value_numbering.h" +#include "gtest/gtest.h" + +namespace art { + +class LocalValueNumberingTest : public testing::Test { + protected: + struct IFieldDef { + uint16_t field_idx; + uintptr_t declaring_dex_file; + uint16_t declaring_field_idx; + bool is_volatile; + }; + + struct SFieldDef { + uint16_t field_idx; + uintptr_t declaring_dex_file; + uint16_t declaring_field_idx; + bool is_volatile; + }; + + struct MIRDef { + static constexpr size_t kMaxSsaDefs = 2; + static constexpr size_t kMaxSsaUses = 4; + + Instruction::Code opcode; + int64_t value; + uint32_t field_info; + size_t num_uses; + int32_t uses[kMaxSsaUses]; + size_t num_defs; + int32_t defs[kMaxSsaDefs]; + }; + +#define DEF_CONST(opcode, reg, value) \ + { opcode, value, 0u, 0, { }, 1, { reg } } +#define DEF_CONST_WIDE(opcode, reg, value) \ + { opcode, value, 0u, 0, { }, 2, { reg, reg + 1 } } +#define DEF_CONST_STRING(opcode, reg, index) \ + { opcode, index, 0u, 0, { }, 1, { reg } } +#define DEF_IGET(opcode, reg, obj, field_info) \ + { opcode, 0u, field_info, 1, { obj }, 1, { reg } } +#define DEF_IGET_WIDE(opcode, reg, obj, field_info) \ + { opcode, 0u, field_info, 1, { obj }, 2, { reg, reg + 1 } } +#define DEF_IPUT(opcode, reg, obj, field_info) \ + { opcode, 0u, field_info, 2, { reg, obj }, 0, { } } +#define DEF_IPUT_WIDE(opcode, reg, obj, field_info) \ + { opcode, 0u, field_info, 3, { reg, reg + 1, obj }, 0, { } } +#define DEF_SGET(opcode, reg, field_info) \ + { opcode, 0u, field_info, 0, { }, 1, { reg } } +#define DEF_SGET_WIDE(opcode, reg, field_info) \ + { opcode, 0u, field_info, 0, { }, 2, { reg, reg + 1 } } +#define DEF_SPUT(opcode, reg, field_info) \ + { opcode, 0u, field_info, 1, { reg }, 0, { } } +#define DEF_SPUT_WIDE(opcode, reg, field_info) \ + { opcode, 0u, field_info, 2, { reg, reg + 1 }, 0, { } } +#define DEF_AGET(opcode, reg, obj, idx) \ + { opcode, 0u, 0u, 2, { obj, idx }, 1, { reg } } +#define DEF_AGET_WIDE(opcode, reg, obj, idx) \ + { opcode, 0u, 0u, 2, { obj, idx }, 2, { reg, reg + 1 } } +#define DEF_APUT(opcode, reg, obj, idx) \ + { opcode, 0u, 0u, 3, { reg, obj, idx }, 0, { } } +#define DEF_APUT_WIDE(opcode, reg, obj, idx) \ + { opcode, 0u, 0u, 4, { reg, reg + 1, obj, idx }, 0, { } } +#define DEF_INVOKE1(opcode, reg) \ + { opcode, 0u, 0u, 1, { reg }, 0, { } } +#define DEF_UNIQUE_REF(opcode, reg) \ + { opcode, 0u, 0u, 0, { }, 1, { reg } } // CONST_CLASS, CONST_STRING, NEW_ARRAY, ... + + void DoPrepareIFields(const IFieldDef* defs, size_t count) { + cu_.mir_graph->ifield_lowering_infos_.Reset(); + cu_.mir_graph->ifield_lowering_infos_.Resize(count); + for (size_t i = 0u; i != count; ++i) { + const IFieldDef* def = &defs[i]; + MirIFieldLoweringInfo field_info(def->field_idx); + if (def->declaring_dex_file != 0u) { + field_info.declaring_dex_file_ = reinterpret_cast(def->declaring_dex_file); + field_info.declaring_field_idx_ = def->declaring_field_idx; + field_info.flags_ = 0u | // Without kFlagIsStatic. + (def->is_volatile ? MirIFieldLoweringInfo::kFlagIsVolatile : 0u); + } + cu_.mir_graph->ifield_lowering_infos_.Insert(field_info); + } + } + + template + void PrepareIFields(const IFieldDef (&defs)[count]) { + DoPrepareIFields(defs, count); + } + + void DoPrepareSFields(const SFieldDef* defs, size_t count) { + cu_.mir_graph->sfield_lowering_infos_.Reset(); + cu_.mir_graph->sfield_lowering_infos_.Resize(count); + for (size_t i = 0u; i != count; ++i) { + const SFieldDef* def = &defs[i]; + MirSFieldLoweringInfo field_info(def->field_idx); + // Mark even unresolved fields as initialized. + field_info.flags_ = MirSFieldLoweringInfo::kFlagIsStatic | + MirSFieldLoweringInfo::kFlagIsInitialized; + if (def->declaring_dex_file != 0u) { + field_info.declaring_dex_file_ = reinterpret_cast(def->declaring_dex_file); + field_info.declaring_field_idx_ = def->declaring_field_idx; + field_info.flags_ |= (def->is_volatile ? MirSFieldLoweringInfo::kFlagIsVolatile : 0u); + } + cu_.mir_graph->sfield_lowering_infos_.Insert(field_info); + } + } + + template + void PrepareSFields(const SFieldDef (&defs)[count]) { + DoPrepareSFields(defs, count); + } + + void DoPrepareMIRs(const MIRDef* defs, size_t count) { + mir_count_ = count; + mirs_ = reinterpret_cast(cu_.arena.Alloc(sizeof(MIR) * count, kArenaAllocMIR)); + ssa_reps_.resize(count); + for (size_t i = 0u; i != count; ++i) { + const MIRDef* def = &defs[i]; + MIR* mir = &mirs_[i]; + mir->dalvikInsn.opcode = def->opcode; + mir->dalvikInsn.vB = static_cast(def->value); + mir->dalvikInsn.vB_wide = def->value; + if (def->opcode >= Instruction::IGET && def->opcode <= Instruction::IPUT_SHORT) { + ASSERT_LT(def->field_info, cu_.mir_graph->ifield_lowering_infos_.Size()); + mir->meta.ifield_lowering_info = def->field_info; + } else if (def->opcode >= Instruction::SGET && def->opcode <= Instruction::SPUT_SHORT) { + ASSERT_LT(def->field_info, cu_.mir_graph->sfield_lowering_infos_.Size()); + mir->meta.sfield_lowering_info = def->field_info; + } + mir->ssa_rep = &ssa_reps_[i]; + mir->ssa_rep->num_uses = def->num_uses; + mir->ssa_rep->uses = const_cast(def->uses); // Not modified by LVN. + mir->ssa_rep->fp_use = nullptr; // Not used by LVN. + mir->ssa_rep->num_defs = def->num_defs; + mir->ssa_rep->defs = const_cast(def->defs); // Not modified by LVN. + mir->ssa_rep->fp_def = nullptr; // Not used by LVN. + mir->dalvikInsn.opcode = def->opcode; + mir->offset = i; // LVN uses offset only for debug output + mir->optimization_flags = 0u; + + if (i != 0u) { + mirs_[i - 1u].next = mir; + } + } + mirs_[count - 1u].next = nullptr; + } + + template + void PrepareMIRs(const MIRDef (&defs)[count]) { + DoPrepareMIRs(defs, count); + } + + void MakeSFieldUninitialized(uint32_t sfield_index) { + CHECK_LT(sfield_index, cu_.mir_graph->sfield_lowering_infos_.Size()); + cu_.mir_graph->sfield_lowering_infos_.GetRawStorage()[sfield_index].flags_ &= + ~MirSFieldLoweringInfo::kFlagIsInitialized; + } + + void PerformLVN() { + value_names_.resize(mir_count_); + for (size_t i = 0; i != mir_count_; ++i) { + value_names_[i] = lvn_->GetValueNumber(&mirs_[i]); + } + EXPECT_TRUE(gvn_->Good()); + } + + LocalValueNumberingTest() + : pool_(), + cu_(&pool_), + mir_count_(0u), + mirs_(nullptr), + ssa_reps_(), + allocator_(), + gvn_(), + lvn_(), + value_names_() { + cu_.mir_graph.reset(new MIRGraph(&cu_, &cu_.arena)); + allocator_.reset(ScopedArenaAllocator::Create(&cu_.arena_stack)); + gvn_.reset(new (allocator_.get()) GlobalValueNumbering(&cu_, allocator_.get())); + lvn_.reset(new (allocator_.get()) LocalValueNumbering(gvn_.get(), 0u, allocator_.get())); + gvn_->AllowModifications(); + } + + ArenaPool pool_; + CompilationUnit cu_; + size_t mir_count_; + MIR* mirs_; + std::vector ssa_reps_; + std::unique_ptr allocator_; + std::unique_ptr gvn_; + std::unique_ptr lvn_; + std::vector value_names_; +}; + +TEST_F(LocalValueNumberingTest, IGetIGetInvokeIGet) { + static const IFieldDef ifields[] = { + { 1u, 1u, 1u, false }, + }; + static const MIRDef mirs[] = { + DEF_IGET(Instruction::IGET, 0u, 10u, 0u), + DEF_IGET(Instruction::IGET, 1u, 10u, 0u), + DEF_INVOKE1(Instruction::INVOKE_VIRTUAL, 11u), + DEF_IGET(Instruction::IGET, 2u, 10u, 0u), + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 4u); + EXPECT_EQ(value_names_[0], value_names_[1]); + EXPECT_NE(value_names_[0], value_names_[3]); + EXPECT_EQ(mirs_[0].optimization_flags, 0u); + EXPECT_EQ(mirs_[1].optimization_flags, MIR_IGNORE_NULL_CHECK); + EXPECT_EQ(mirs_[2].optimization_flags, 0u); + EXPECT_EQ(mirs_[3].optimization_flags, MIR_IGNORE_NULL_CHECK); +} + +TEST_F(LocalValueNumberingTest, IGetIPutIGetIGetIGet) { + static const IFieldDef ifields[] = { + { 1u, 1u, 1u, false }, + { 2u, 1u, 2u, false }, + }; + static const MIRDef mirs[] = { + DEF_IGET(Instruction::IGET_OBJECT, 0u, 10u, 0u), + DEF_IPUT(Instruction::IPUT_OBJECT, 1u, 11u, 0u), // May alias. + DEF_IGET(Instruction::IGET_OBJECT, 2u, 10u, 0u), + DEF_IGET(Instruction::IGET, 3u, 0u, 1u), + DEF_IGET(Instruction::IGET, 4u, 2u, 1u), + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 5u); + EXPECT_NE(value_names_[0], value_names_[2]); + EXPECT_NE(value_names_[3], value_names_[4]); + for (size_t i = 0; i != arraysize(mirs); ++i) { + EXPECT_EQ((i == 2u) ? MIR_IGNORE_NULL_CHECK : 0, + mirs_[i].optimization_flags) << i; + } +} + +TEST_F(LocalValueNumberingTest, UniquePreserve1) { + static const IFieldDef ifields[] = { + { 1u, 1u, 1u, false }, + }; + static const MIRDef mirs[] = { + DEF_UNIQUE_REF(Instruction::NEW_INSTANCE, 10u), + DEF_IGET(Instruction::IGET, 0u, 10u, 0u), + DEF_IPUT(Instruction::IPUT, 1u, 11u, 0u), // No aliasing since 10u is unique. + DEF_IGET(Instruction::IGET, 2u, 10u, 0u), + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 4u); + EXPECT_EQ(value_names_[1], value_names_[3]); + for (size_t i = 0; i != arraysize(mirs); ++i) { + EXPECT_EQ((i == 1u || i == 3u) ? MIR_IGNORE_NULL_CHECK : 0, + mirs_[i].optimization_flags) << i; + } +} + +TEST_F(LocalValueNumberingTest, UniquePreserve2) { + static const IFieldDef ifields[] = { + { 1u, 1u, 1u, false }, + }; + static const MIRDef mirs[] = { + DEF_UNIQUE_REF(Instruction::NEW_INSTANCE, 11u), + DEF_IGET(Instruction::IGET, 0u, 10u, 0u), + DEF_IPUT(Instruction::IPUT, 1u, 11u, 0u), // No aliasing since 11u is unique. + DEF_IGET(Instruction::IGET, 2u, 10u, 0u), + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 4u); + EXPECT_EQ(value_names_[1], value_names_[3]); + for (size_t i = 0; i != arraysize(mirs); ++i) { + EXPECT_EQ((i == 2u || i == 3u) ? MIR_IGNORE_NULL_CHECK : 0, + mirs_[i].optimization_flags) << i; + } +} + +TEST_F(LocalValueNumberingTest, UniquePreserveAndEscape) { + static const IFieldDef ifields[] = { + { 1u, 1u, 1u, false }, + }; + static const MIRDef mirs[] = { + DEF_UNIQUE_REF(Instruction::NEW_INSTANCE, 10u), + DEF_IGET(Instruction::IGET, 0u, 10u, 0u), + DEF_INVOKE1(Instruction::INVOKE_VIRTUAL, 11u), // 10u still unique. + DEF_IGET(Instruction::IGET, 2u, 10u, 0u), + DEF_INVOKE1(Instruction::INVOKE_VIRTUAL, 10u), // 10u not unique anymore. + DEF_IGET(Instruction::IGET, 3u, 10u, 0u), + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 6u); + EXPECT_EQ(value_names_[1], value_names_[3]); + EXPECT_NE(value_names_[1], value_names_[5]); + for (size_t i = 0; i != arraysize(mirs); ++i) { + EXPECT_EQ((i == 1u || i == 3u || i == 4u || i == 5u) ? MIR_IGNORE_NULL_CHECK : 0, + mirs_[i].optimization_flags) << i; + } +} + +TEST_F(LocalValueNumberingTest, Volatile) { + static const IFieldDef ifields[] = { + { 1u, 1u, 1u, false }, + { 2u, 1u, 2u, true }, + }; + static const MIRDef mirs[] = { + DEF_IGET(Instruction::IGET, 0u, 10u, 1u), // Volatile. + DEF_IGET(Instruction::IGET, 1u, 0u, 0u), // Non-volatile. + DEF_IGET(Instruction::IGET, 2u, 10u, 1u), // Volatile. + DEF_IGET(Instruction::IGET, 3u, 2u, 1u), // Non-volatile. + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 4u); + EXPECT_NE(value_names_[0], value_names_[2]); // Volatile has always different value name. + EXPECT_NE(value_names_[1], value_names_[3]); // Used different base because of volatile. + for (size_t i = 0; i != arraysize(mirs); ++i) { + EXPECT_EQ((i == 2u) ? MIR_IGNORE_NULL_CHECK : 0, + mirs_[i].optimization_flags) << i; + } +} + +TEST_F(LocalValueNumberingTest, UnresolvedIField) { + static const IFieldDef ifields[] = { + { 1u, 1u, 1u, false }, // Resolved field #1. + { 2u, 1u, 2u, false }, // Resolved field #2. + { 3u, 0u, 0u, false }, // Unresolved field. + }; + static const MIRDef mirs[] = { + DEF_UNIQUE_REF(Instruction::NEW_INSTANCE, 20u), + DEF_IGET(Instruction::IGET, 1u, 20u, 0u), // Resolved field #1, unique object. + DEF_IGET(Instruction::IGET, 2u, 21u, 0u), // Resolved field #1. + DEF_IGET_WIDE(Instruction::IGET_WIDE, 3u, 21u, 1u), // Resolved field #2. + DEF_IGET(Instruction::IGET, 4u, 22u, 2u), // IGET doesn't clobber anything. + DEF_IGET(Instruction::IGET, 5u, 20u, 0u), // Resolved field #1, unique object. + DEF_IGET(Instruction::IGET, 6u, 21u, 0u), // Resolved field #1. + DEF_IGET_WIDE(Instruction::IGET_WIDE, 7u, 21u, 1u), // Resolved field #2. + DEF_IPUT(Instruction::IPUT, 8u, 22u, 2u), // IPUT clobbers field #1 (#2 is wide). + DEF_IGET(Instruction::IGET, 9u, 20u, 0u), // Resolved field #1, unique object. + DEF_IGET(Instruction::IGET, 10u, 21u, 0u), // Resolved field #1, new value name. + DEF_IGET_WIDE(Instruction::IGET_WIDE, 11u, 21u, 1u), // Resolved field #2. + DEF_IGET_WIDE(Instruction::IGET_WIDE, 12u, 20u, 1u), // Resolved field #2, unique object. + DEF_IPUT(Instruction::IPUT, 13u, 20u, 2u), // IPUT clobbers field #1 (#2 is wide). + DEF_IGET(Instruction::IGET, 14u, 20u, 0u), // Resolved field #1, unique object. + DEF_IGET_WIDE(Instruction::IGET_WIDE, 15u, 20u, 1u), // Resolved field #2, unique object. + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 16u); + EXPECT_EQ(value_names_[1], value_names_[5]); + EXPECT_EQ(value_names_[2], value_names_[6]); + EXPECT_EQ(value_names_[3], value_names_[7]); + EXPECT_EQ(value_names_[1], value_names_[9]); + EXPECT_NE(value_names_[2], value_names_[10]); // This aliased with unresolved IPUT. + EXPECT_EQ(value_names_[3], value_names_[11]); + EXPECT_EQ(value_names_[12], value_names_[15]); + EXPECT_NE(value_names_[1], value_names_[14]); // This aliased with unresolved IPUT. + EXPECT_EQ(mirs_[0].optimization_flags, 0u); + EXPECT_EQ(mirs_[1].optimization_flags, MIR_IGNORE_NULL_CHECK); + EXPECT_EQ(mirs_[2].optimization_flags, 0u); + EXPECT_EQ(mirs_[3].optimization_flags, MIR_IGNORE_NULL_CHECK); + EXPECT_EQ(mirs_[4].optimization_flags, 0u); + for (size_t i = 5u; i != mir_count_; ++i) { + EXPECT_EQ((i == 1u || i == 3u || i >=5u) ? MIR_IGNORE_NULL_CHECK : 0, + mirs_[i].optimization_flags) << i; + } +} + +TEST_F(LocalValueNumberingTest, UnresolvedSField) { + static const SFieldDef sfields[] = { + { 1u, 1u, 1u, false }, // Resolved field #1. + { 2u, 1u, 2u, false }, // Resolved field #2. + { 3u, 0u, 0u, false }, // Unresolved field. + }; + static const MIRDef mirs[] = { + DEF_SGET(Instruction::SGET, 0u, 0u), // Resolved field #1. + DEF_SGET_WIDE(Instruction::SGET_WIDE, 1u, 1u), // Resolved field #2. + DEF_SGET(Instruction::SGET, 2u, 2u), // SGET doesn't clobber anything. + DEF_SGET(Instruction::SGET, 3u, 0u), // Resolved field #1. + DEF_SGET_WIDE(Instruction::SGET_WIDE, 4u, 1u), // Resolved field #2. + DEF_SPUT(Instruction::SPUT, 5u, 2u), // SPUT clobbers field #1 (#2 is wide). + DEF_SGET(Instruction::SGET, 6u, 0u), // Resolved field #1. + DEF_SGET_WIDE(Instruction::SGET_WIDE, 7u, 1u), // Resolved field #2. + }; + + PrepareSFields(sfields); + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 8u); + EXPECT_EQ(value_names_[0], value_names_[3]); + EXPECT_EQ(value_names_[1], value_names_[4]); + EXPECT_NE(value_names_[0], value_names_[6]); // This aliased with unresolved IPUT. + EXPECT_EQ(value_names_[1], value_names_[7]); + for (size_t i = 0u; i != mir_count_; ++i) { + EXPECT_EQ(0, mirs_[i].optimization_flags) << i; + } +} + +TEST_F(LocalValueNumberingTest, UninitializedSField) { + static const IFieldDef ifields[] = { + { 1u, 1u, 1u, false }, // Resolved field #1. + }; + static const SFieldDef sfields[] = { + { 1u, 1u, 1u, false }, // Resolved field #1. + { 2u, 1u, 2u, false }, // Resolved field #2; uninitialized. + }; + static const MIRDef mirs[] = { + DEF_UNIQUE_REF(Instruction::NEW_INSTANCE, 200u), + DEF_IGET(Instruction::IGET, 1u, 100u, 0u), + DEF_IGET(Instruction::IGET, 2u, 200u, 0u), + DEF_SGET(Instruction::SGET, 3u, 0u), + DEF_SGET(Instruction::SGET, 4u, 1u), // Can call (). + DEF_IGET(Instruction::IGET, 5u, 100u, 0u), // Differs from 1u. + DEF_IGET(Instruction::IGET, 6u, 200u, 0u), // Same as 2u. + DEF_SGET(Instruction::SGET, 7u, 0u), // Differs from 3u. + }; + + PrepareIFields(ifields); + PrepareSFields(sfields); + MakeSFieldUninitialized(1u); + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 8u); + EXPECT_NE(value_names_[1], value_names_[5]); + EXPECT_EQ(value_names_[2], value_names_[6]); + EXPECT_NE(value_names_[3], value_names_[7]); +} + +TEST_F(LocalValueNumberingTest, ConstString) { + static const MIRDef mirs[] = { + DEF_CONST_STRING(Instruction::CONST_STRING, 0u, 0u), + DEF_CONST_STRING(Instruction::CONST_STRING, 1u, 0u), + DEF_CONST_STRING(Instruction::CONST_STRING, 2u, 2u), + DEF_CONST_STRING(Instruction::CONST_STRING, 3u, 0u), + DEF_INVOKE1(Instruction::INVOKE_DIRECT, 2u), + DEF_CONST_STRING(Instruction::CONST_STRING, 4u, 2u), + }; + + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 6u); + EXPECT_EQ(value_names_[1], value_names_[0]); + EXPECT_NE(value_names_[2], value_names_[0]); + EXPECT_EQ(value_names_[3], value_names_[0]); + EXPECT_EQ(value_names_[5], value_names_[2]); +} + +TEST_F(LocalValueNumberingTest, SameValueInDifferentMemoryLocations) { + static const IFieldDef ifields[] = { + { 1u, 1u, 1u, false }, + { 2u, 1u, 2u, false }, + }; + static const SFieldDef sfields[] = { + { 3u, 1u, 3u, false }, + }; + static const MIRDef mirs[] = { + DEF_UNIQUE_REF(Instruction::NEW_ARRAY, 201u), + DEF_IGET(Instruction::IGET, 0u, 100u, 0u), + DEF_IPUT(Instruction::IPUT, 0u, 100u, 1u), + DEF_IPUT(Instruction::IPUT, 0u, 101u, 1u), + DEF_APUT(Instruction::APUT, 0u, 200u, 300u), + DEF_APUT(Instruction::APUT, 0u, 200u, 301u), + DEF_APUT(Instruction::APUT, 0u, 201u, 300u), + DEF_APUT(Instruction::APUT, 0u, 201u, 301u), + DEF_SPUT(Instruction::SPUT, 0u, 0u), + DEF_IGET(Instruction::IGET, 9u, 100u, 0u), + DEF_IGET(Instruction::IGET, 10u, 100u, 1u), + DEF_IGET(Instruction::IGET, 11u, 101u, 1u), + DEF_AGET(Instruction::AGET, 12u, 200u, 300u), + DEF_AGET(Instruction::AGET, 13u, 200u, 301u), + DEF_AGET(Instruction::AGET, 14u, 201u, 300u), + DEF_AGET(Instruction::AGET, 15u, 201u, 301u), + DEF_SGET(Instruction::SGET, 16u, 0u), + }; + + PrepareIFields(ifields); + PrepareSFields(sfields); + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 17u); + for (size_t i = 9; i != arraysize(mirs); ++i) { + EXPECT_EQ(value_names_[1], value_names_[i]) << i; + } + for (size_t i = 0; i != arraysize(mirs); ++i) { + int expected_flags = + ((i == 2u || (i >= 5u && i <= 7u) || (i >= 9u && i <= 15u)) ? MIR_IGNORE_NULL_CHECK : 0) | + ((i >= 12u && i <= 15u) ? MIR_IGNORE_RANGE_CHECK : 0); + EXPECT_EQ(expected_flags, mirs_[i].optimization_flags) << i; + } +} + +TEST_F(LocalValueNumberingTest, UniqueArrayAliasing) { + static const MIRDef mirs[] = { + DEF_UNIQUE_REF(Instruction::NEW_ARRAY, 20u), + DEF_AGET(Instruction::AGET, 1u, 20u, 40u), + DEF_APUT(Instruction::APUT, 2u, 20u, 41u), // May alias with index for sreg 40u. + DEF_AGET(Instruction::AGET, 3u, 20u, 40u), + }; + + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 4u); + EXPECT_NE(value_names_[1], value_names_[3]); + for (size_t i = 0; i != arraysize(mirs); ++i) { + int expected_flags = + ((i >= 1u) ? MIR_IGNORE_NULL_CHECK : 0) | + ((i == 3u) ? MIR_IGNORE_RANGE_CHECK : 0); + EXPECT_EQ(expected_flags, mirs_[i].optimization_flags) << i; + } +} + +TEST_F(LocalValueNumberingTest, EscapingRefs) { + static const IFieldDef ifields[] = { + { 1u, 1u, 1u, false }, // Field #1. + { 2u, 1u, 2u, false }, // Field #2. + { 3u, 1u, 3u, false }, // Reference field for storing escaping refs. + { 4u, 1u, 4u, false }, // Wide. + { 5u, 0u, 0u, false }, // Unresolved field, int. + { 6u, 0u, 0u, false }, // Unresolved field, wide. + }; + static const MIRDef mirs[] = { + DEF_UNIQUE_REF(Instruction::NEW_INSTANCE, 20u), + DEF_IGET(Instruction::IGET, 1u, 20u, 0u), + DEF_IGET(Instruction::IGET, 2u, 20u, 1u), + DEF_IPUT(Instruction::IPUT_OBJECT, 20u, 30u, 2u), // Ref escapes. + DEF_IGET(Instruction::IGET, 4u, 20u, 0u), + DEF_IGET(Instruction::IGET, 5u, 20u, 1u), + DEF_IPUT(Instruction::IPUT, 6u, 31u, 0u), // May alias with field #1. + DEF_IGET(Instruction::IGET, 7u, 20u, 0u), // New value. + DEF_IGET(Instruction::IGET, 8u, 20u, 1u), // Still the same. + DEF_IPUT_WIDE(Instruction::IPUT_WIDE, 9u, 31u, 3u), // No aliasing, different type. + DEF_IGET(Instruction::IGET, 10u, 20u, 0u), + DEF_IGET(Instruction::IGET, 11u, 20u, 1u), + DEF_IPUT_WIDE(Instruction::IPUT_WIDE, 12u, 31u, 5u), // No aliasing, different type. + DEF_IGET(Instruction::IGET, 13u, 20u, 0u), + DEF_IGET(Instruction::IGET, 14u, 20u, 1u), + DEF_IPUT(Instruction::IPUT, 15u, 31u, 4u), // Aliasing, same type. + DEF_IGET(Instruction::IGET, 16u, 20u, 0u), + DEF_IGET(Instruction::IGET, 17u, 20u, 1u), + }; + + PrepareIFields(ifields); + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 18u); + EXPECT_EQ(value_names_[1], value_names_[4]); + EXPECT_EQ(value_names_[2], value_names_[5]); + EXPECT_NE(value_names_[4], value_names_[7]); // New value. + EXPECT_EQ(value_names_[5], value_names_[8]); + EXPECT_EQ(value_names_[7], value_names_[10]); + EXPECT_EQ(value_names_[8], value_names_[11]); + EXPECT_EQ(value_names_[10], value_names_[13]); + EXPECT_EQ(value_names_[11], value_names_[14]); + EXPECT_NE(value_names_[13], value_names_[16]); // New value. + EXPECT_NE(value_names_[14], value_names_[17]); // New value. + for (size_t i = 0u; i != mir_count_; ++i) { + int expected = (i != 0u && i != 3u && i != 6u) ? MIR_IGNORE_NULL_CHECK : 0; + EXPECT_EQ(expected, mirs_[i].optimization_flags) << i; + } +} + +TEST_F(LocalValueNumberingTest, EscapingArrayRefs) { + static const MIRDef mirs[] = { + DEF_UNIQUE_REF(Instruction::NEW_ARRAY, 20u), + DEF_AGET(Instruction::AGET, 1u, 20u, 40u), + DEF_AGET(Instruction::AGET, 2u, 20u, 41u), + DEF_APUT(Instruction::APUT_OBJECT, 20u, 30u, 42u), // Array ref escapes. + DEF_AGET(Instruction::AGET, 4u, 20u, 40u), + DEF_AGET(Instruction::AGET, 5u, 20u, 41u), + DEF_APUT_WIDE(Instruction::APUT_WIDE, 6u, 31u, 43u), // No aliasing, different type. + DEF_AGET(Instruction::AGET, 7u, 20u, 40u), + DEF_AGET(Instruction::AGET, 8u, 20u, 41u), + DEF_APUT(Instruction::APUT, 9u, 32u, 40u), // May alias with all elements. + DEF_AGET(Instruction::AGET, 10u, 20u, 40u), // New value (same index name). + DEF_AGET(Instruction::AGET, 11u, 20u, 41u), // New value (different index name). + }; + + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 12u); + EXPECT_EQ(value_names_[1], value_names_[4]); + EXPECT_EQ(value_names_[2], value_names_[5]); + EXPECT_EQ(value_names_[4], value_names_[7]); + EXPECT_EQ(value_names_[5], value_names_[8]); + EXPECT_NE(value_names_[7], value_names_[10]); // New value. + EXPECT_NE(value_names_[8], value_names_[11]); // New value. + for (size_t i = 0u; i != mir_count_; ++i) { + int expected = + ((i != 0u && i != 3u && i != 6u && i != 9u) ? MIR_IGNORE_NULL_CHECK : 0u) | + ((i >= 4 && i != 6u && i != 9u) ? MIR_IGNORE_RANGE_CHECK : 0u); + EXPECT_EQ(expected, mirs_[i].optimization_flags) << i; + } +} + +TEST_F(LocalValueNumberingTest, StoringSameValueKeepsMemoryVersion) { + static const IFieldDef ifields[] = { + { 1u, 1u, 1u, false }, + { 2u, 1u, 2u, false }, + }; + static const SFieldDef sfields[] = { + { 2u, 1u, 2u, false }, + }; + static const MIRDef mirs[] = { + DEF_IGET(Instruction::IGET, 0u, 30u, 0u), + DEF_IGET(Instruction::IGET, 1u, 31u, 0u), + DEF_IPUT(Instruction::IPUT, 1u, 31u, 0u), // Store the same value. + DEF_IGET(Instruction::IGET, 3u, 30u, 0u), + DEF_AGET(Instruction::AGET, 4u, 32u, 40u), + DEF_AGET(Instruction::AGET, 5u, 33u, 40u), + DEF_APUT(Instruction::APUT, 5u, 33u, 40u), // Store the same value. + DEF_AGET(Instruction::AGET, 7u, 32u, 40u), + DEF_SGET(Instruction::SGET, 8u, 0u), + DEF_SPUT(Instruction::SPUT, 8u, 0u), // Store the same value. + DEF_SGET(Instruction::SGET, 10u, 0u), + DEF_UNIQUE_REF(Instruction::NEW_INSTANCE, 50u), // Test with unique references. + { Instruction::FILLED_NEW_ARRAY, 0, 0u, 2, { 12u, 13u }, 0, { } }, + DEF_UNIQUE_REF(Instruction::MOVE_RESULT_OBJECT, 51u), + DEF_IGET(Instruction::IGET, 14u, 50u, 0u), + DEF_IGET(Instruction::IGET, 15u, 50u, 1u), + DEF_IPUT(Instruction::IPUT, 15u, 50u, 1u), // Store the same value. + DEF_IGET(Instruction::IGET, 17u, 50u, 0u), + DEF_AGET(Instruction::AGET, 18u, 51u, 40u), + DEF_AGET(Instruction::AGET, 19u, 51u, 41u), + DEF_APUT(Instruction::APUT, 19u, 51u, 41u), // Store the same value. + DEF_AGET(Instruction::AGET, 21u, 51u, 40u), + }; + + PrepareIFields(ifields); + PrepareSFields(sfields); + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 22u); + EXPECT_NE(value_names_[0], value_names_[1]); + EXPECT_EQ(value_names_[0], value_names_[3]); + EXPECT_NE(value_names_[4], value_names_[5]); + EXPECT_EQ(value_names_[4], value_names_[7]); + EXPECT_EQ(value_names_[8], value_names_[10]); + EXPECT_NE(value_names_[14], value_names_[15]); + EXPECT_EQ(value_names_[14], value_names_[17]); + EXPECT_NE(value_names_[18], value_names_[19]); + EXPECT_EQ(value_names_[18], value_names_[21]); + for (size_t i = 0u; i != mir_count_; ++i) { + int expected = + ((i == 2u || i == 3u || i == 6u || i == 7u || (i >= 14u)) ? MIR_IGNORE_NULL_CHECK : 0u) | + ((i == 6u || i == 7u || i >= 20u) ? MIR_IGNORE_RANGE_CHECK : 0u); + EXPECT_EQ(expected, mirs_[i].optimization_flags) << i; + } +} + +TEST_F(LocalValueNumberingTest, FilledNewArrayTracking) { + if (!kLocalValueNumberingEnableFilledNewArrayTracking) { + // Feature disabled. + return; + } + static const MIRDef mirs[] = { + DEF_CONST(Instruction::CONST, 0u, 100), + DEF_CONST(Instruction::CONST, 1u, 200), + { Instruction::FILLED_NEW_ARRAY, 0, 0u, 2, { 0u, 1u }, 0, { } }, + DEF_UNIQUE_REF(Instruction::MOVE_RESULT_OBJECT, 10u), + DEF_CONST(Instruction::CONST, 20u, 0), + DEF_CONST(Instruction::CONST, 21u, 1), + DEF_AGET(Instruction::AGET, 6u, 10u, 20u), + DEF_AGET(Instruction::AGET, 7u, 10u, 21u), + }; + + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 8u); + EXPECT_EQ(value_names_[0], value_names_[6]); + EXPECT_EQ(value_names_[1], value_names_[7]); + for (size_t i = 0u; i != mir_count_; ++i) { + int expected = (i == 6u || i == 7u) ? (MIR_IGNORE_NULL_CHECK | MIR_IGNORE_RANGE_CHECK) : 0u; + EXPECT_EQ(expected, mirs_[i].optimization_flags) << i; + } +} + +TEST_F(LocalValueNumberingTest, ClInitOnSget) { + static const SFieldDef sfields[] = { + { 0u, 1u, 0u, false }, + { 1u, 2u, 1u, false }, + }; + static const MIRDef mirs[] = { + DEF_SGET(Instruction::SGET_OBJECT, 0u, 0u), + DEF_AGET(Instruction::AGET, 1u, 0u, 100u), + DEF_SGET(Instruction::SGET_OBJECT, 2u, 1u), + DEF_SGET(Instruction::SGET_OBJECT, 3u, 0u), + DEF_AGET(Instruction::AGET, 4u, 3u, 100u), + }; + + PrepareSFields(sfields); + MakeSFieldUninitialized(1u); + PrepareMIRs(mirs); + PerformLVN(); + ASSERT_EQ(value_names_.size(), 5u); + EXPECT_NE(value_names_[0], value_names_[3]); +} + +} // namespace art diff --git a/compiler/dex/mir_analysis.cc b/compiler/dex/mir_analysis.cc index d7a4136a01a..3559869ec82 100644 --- a/compiler/dex/mir_analysis.cc +++ b/compiler/dex/mir_analysis.cc @@ -14,8 +14,18 @@ * limitations under the License. */ +#include +#include + #include "compiler_internals.h" #include "dataflow_iterator-inl.h" +#include "dex_instruction.h" +#include "dex_instruction-inl.h" +#include "dex/verified_method.h" +#include "dex/quick/dex_file_method_inliner.h" +#include "dex/quick/dex_file_to_method_inliner_map.h" +#include "driver/compiler_options.h" +#include "utils/scoped_arena_containers.h" namespace art { @@ -366,8 +376,8 @@ const uint32_t MIRGraph::analysis_attributes_[kMirOpLast] = { // 72 INVOKE_INTERFACE {vD, vE, vF, vG, vA} AN_INVOKE | AN_HEAVYWEIGHT, - // 73 UNUSED_73 - AN_NONE, + // 73 RETURN_VOID_BARRIER: + AN_BRANCH, // 74 INVOKE_VIRTUAL_RANGE {vCCCC .. vNNNN} AN_INVOKE | AN_HEAVYWEIGHT, @@ -864,7 +874,7 @@ void MIRGraph::AnalyzeBlock(BasicBlock* bb, MethodStats* stats) { if (ending_bb->last_mir_insn != NULL) { uint32_t ending_flags = analysis_attributes_[ending_bb->last_mir_insn->dalvikInsn.opcode]; while ((ending_flags & AN_BRANCH) == 0) { - ending_bb = ending_bb->fall_through; + ending_bb = GetBasicBlock(ending_bb->fall_through); ending_flags = analysis_attributes_[ending_bb->last_mir_insn->dalvikInsn.opcode]; } } @@ -876,13 +886,14 @@ void MIRGraph::AnalyzeBlock(BasicBlock* bb, MethodStats* stats) { */ int loop_scale_factor = 1; // Simple for and while loops - if ((ending_bb->taken != NULL) && (ending_bb->fall_through == NULL)) { - if ((ending_bb->taken->taken == bb) || (ending_bb->taken->fall_through == bb)) { + if ((ending_bb->taken != NullBasicBlockId) && (ending_bb->fall_through == NullBasicBlockId)) { + if ((GetBasicBlock(ending_bb->taken)->taken == bb->id) || + (GetBasicBlock(ending_bb->taken)->fall_through == bb->id)) { loop_scale_factor = 25; } } // Simple do-while loop - if ((ending_bb->taken != NULL) && (ending_bb->taken == bb)) { + if ((ending_bb->taken != NullBasicBlockId) && (ending_bb->taken == bb->id)) { loop_scale_factor = 25; } @@ -891,7 +902,7 @@ void MIRGraph::AnalyzeBlock(BasicBlock* bb, MethodStats* stats) { while (!done) { tbb->visited = true; for (MIR* mir = tbb->first_mir_insn; mir != NULL; mir = mir->next) { - if (static_cast(mir->dalvikInsn.opcode) >= kMirOpFirst) { + if (MIR::DecodedInstruction::IsPseudoMirOp(mir->dalvikInsn.opcode)) { // Skip any MIR pseudo-op. continue; } @@ -922,7 +933,7 @@ void MIRGraph::AnalyzeBlock(BasicBlock* bb, MethodStats* stats) { if (tbb == ending_bb) { done = true; } else { - tbb = tbb->fall_through; + tbb = GetBasicBlock(tbb->fall_through); } } if (has_math && computational_block && (loop_scale_factor > 1)) { @@ -930,7 +941,8 @@ void MIRGraph::AnalyzeBlock(BasicBlock* bb, MethodStats* stats) { } } -bool MIRGraph::ComputeSkipCompilation(MethodStats* stats, bool skip_default) { +bool MIRGraph::ComputeSkipCompilation(MethodStats* stats, bool skip_default, + std::string* skip_message) { float count = stats->dex_instructions; stats->math_ratio = stats->math_ops / count; stats->fp_ratio = stats->fp_ops / count; @@ -955,7 +967,7 @@ bool MIRGraph::ComputeSkipCompilation(MethodStats* stats, bool skip_default) { } // Complex, logic-intensive? - if ((GetNumDalvikInsns() > Runtime::Current()->GetSmallMethodThreshold()) && + if (cu_->compiler_driver->GetCompilerOptions().IsSmallMethod(GetNumDalvikInsns()) && stats->branch_ratio > 0.3) { return false; } @@ -981,8 +993,10 @@ bool MIRGraph::ComputeSkipCompilation(MethodStats* stats, bool skip_default) { } // If significant in size and high proportion of expensive operations, skip. - if ((GetNumDalvikInsns() > Runtime::Current()->GetSmallMethodThreshold()) && + if (cu_->compiler_driver->GetCompilerOptions().IsSmallMethod(GetNumDalvikInsns()) && (stats->heavyweight_ratio > 0.3)) { + *skip_message = "Is a small method with heavyweight ratio " + + std::to_string(stats->heavyweight_ratio); return true; } @@ -991,15 +1005,22 @@ bool MIRGraph::ComputeSkipCompilation(MethodStats* stats, bool skip_default) { /* * Will eventually want this to be a bit more sophisticated and happen at verification time. - * Ultimate goal is to drive with profile data. */ -bool MIRGraph::SkipCompilation(Runtime::CompilerFilter compiler_filter) { - if (compiler_filter == Runtime::kEverything) { +bool MIRGraph::SkipCompilation(std::string* skip_message) { + const CompilerOptions& compiler_options = cu_->compiler_driver->GetCompilerOptions(); + CompilerOptions::CompilerFilter compiler_filter = compiler_options.GetCompilerFilter(); + if (compiler_filter == CompilerOptions::kEverything) { return false; } - if (compiler_filter == Runtime::kInterpretOnly) { - LOG(WARNING) << "InterpretOnly should ideally be filtered out prior to parsing."; + // Contains a pattern we don't want to compile? + if (PuntToInterpreter()) { + *skip_message = "Punt to interpreter set"; + return true; + } + + if (!compiler_options.IsCompilationEnabled()) { + *skip_message = "Compilation disabled"; return true; } @@ -1007,17 +1028,17 @@ bool MIRGraph::SkipCompilation(Runtime::CompilerFilter compiler_filter) { size_t small_cutoff = 0; size_t default_cutoff = 0; switch (compiler_filter) { - case Runtime::kBalanced: - small_cutoff = Runtime::Current()->GetSmallMethodThreshold(); - default_cutoff = Runtime::Current()->GetLargeMethodThreshold(); + case CompilerOptions::kBalanced: + small_cutoff = compiler_options.GetSmallMethodThreshold(); + default_cutoff = compiler_options.GetLargeMethodThreshold(); break; - case Runtime::kSpace: - small_cutoff = Runtime::Current()->GetTinyMethodThreshold(); - default_cutoff = Runtime::Current()->GetSmallMethodThreshold(); + case CompilerOptions::kSpace: + small_cutoff = compiler_options.GetTinyMethodThreshold(); + default_cutoff = compiler_options.GetSmallMethodThreshold(); break; - case Runtime::kSpeed: - small_cutoff = Runtime::Current()->GetHugeMethodThreshold(); - default_cutoff = Runtime::Current()->GetHugeMethodThreshold(); + case CompilerOptions::kSpeed: + small_cutoff = compiler_options.GetHugeMethodThreshold(); + default_cutoff = compiler_options.GetHugeMethodThreshold(); break; default: LOG(FATAL) << "Unexpected compiler_filter_: " << compiler_filter; @@ -1025,25 +1046,41 @@ bool MIRGraph::SkipCompilation(Runtime::CompilerFilter compiler_filter) { // If size < cutoff, assume we'll compile - but allow removal. bool skip_compilation = (GetNumDalvikInsns() >= default_cutoff); + if (skip_compilation) { + *skip_message = "#Insns >= default_cutoff: " + std::to_string(GetNumDalvikInsns()); + } /* * Filter 1: Huge methods are likely to be machine generated, but some aren't. * If huge, assume we won't compile, but allow futher analysis to turn it back on. */ - if (GetNumDalvikInsns() > Runtime::Current()->GetHugeMethodThreshold()) { + if (compiler_options.IsHugeMethod(GetNumDalvikInsns())) { skip_compilation = true; - } else if (compiler_filter == Runtime::kSpeed) { + *skip_message = "Huge method: " + std::to_string(GetNumDalvikInsns()); + // If we're got a huge number of basic blocks, don't bother with further analysis. + if (static_cast(num_blocks_) > (compiler_options.GetHugeMethodThreshold() / 2)) { + return true; + } + } else if (compiler_options.IsLargeMethod(GetNumDalvikInsns()) && + /* If it's large and contains no branches, it's likely to be machine generated initialization */ + (GetBranchCount() == 0)) { + *skip_message = "Large method with no branches"; + return true; + } else if (compiler_filter == CompilerOptions::kSpeed) { // If not huge, compile. return false; } // Filter 2: Skip class initializers. if (((cu_->access_flags & kAccConstructor) != 0) && ((cu_->access_flags & kAccStatic) != 0)) { + *skip_message = "Class initializer"; return true; } // Filter 3: if this method is a special pattern, go ahead and emit the canned pattern. - if (IsSpecialCase()) { + if (cu_->compiler_driver->GetMethodInlinerMap() != nullptr && + cu_->compiler_driver->GetMethodInlinerMap()->GetMethodInliner(cu_->dex_file) + ->IsSpecial(cu_->method_idx)) { return false; } @@ -1061,12 +1098,206 @@ bool MIRGraph::SkipCompilation(Runtime::CompilerFilter compiler_filter) { memset(&stats, 0, sizeof(stats)); ClearAllVisitedFlags(); - AllNodesIterator iter(this, false /* not iterative */); + AllNodesIterator iter(this); for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { AnalyzeBlock(bb, &stats); } - return ComputeSkipCompilation(&stats, skip_compilation); + return ComputeSkipCompilation(&stats, skip_compilation, skip_message); +} + +void MIRGraph::DoCacheFieldLoweringInfo() { + // All IGET/IPUT/SGET/SPUT instructions take 2 code units and there must also be a RETURN. + const uint32_t max_refs = (current_code_item_->insns_size_in_code_units_ - 1u) / 2u; + ScopedArenaAllocator allocator(&cu_->arena_stack); + uint16_t* field_idxs = + reinterpret_cast(allocator.Alloc(max_refs * sizeof(uint16_t), kArenaAllocMisc)); + + // Find IGET/IPUT/SGET/SPUT insns, store IGET/IPUT fields at the beginning, SGET/SPUT at the end. + size_t ifield_pos = 0u; + size_t sfield_pos = max_refs; + AllNodesIterator iter(this); + for (BasicBlock* bb = iter.Next(); bb != nullptr; bb = iter.Next()) { + if (bb->block_type != kDalvikByteCode) { + continue; + } + for (MIR* mir = bb->first_mir_insn; mir != nullptr; mir = mir->next) { + if (mir->dalvikInsn.opcode >= Instruction::IGET && + mir->dalvikInsn.opcode <= Instruction::SPUT_SHORT) { + const Instruction* insn = Instruction::At(current_code_item_->insns_ + mir->offset); + // Get field index and try to find it among existing indexes. If found, it's usually among + // the last few added, so we'll start the search from ifield_pos/sfield_pos. Though this + // is a linear search, it actually performs much better than map based approach. + if (mir->dalvikInsn.opcode <= Instruction::IPUT_SHORT) { + uint16_t field_idx = insn->VRegC_22c(); + size_t i = ifield_pos; + while (i != 0u && field_idxs[i - 1] != field_idx) { + --i; + } + if (i != 0u) { + mir->meta.ifield_lowering_info = i - 1; + } else { + mir->meta.ifield_lowering_info = ifield_pos; + field_idxs[ifield_pos++] = field_idx; + } + } else { + uint16_t field_idx = insn->VRegB_21c(); + size_t i = sfield_pos; + while (i != max_refs && field_idxs[i] != field_idx) { + ++i; + } + if (i != max_refs) { + mir->meta.sfield_lowering_info = max_refs - i - 1u; + } else { + mir->meta.sfield_lowering_info = max_refs - sfield_pos; + field_idxs[--sfield_pos] = field_idx; + } + } + DCHECK_LE(ifield_pos, sfield_pos); + } + } + } + + if (ifield_pos != 0u) { + // Resolve instance field infos. + DCHECK_EQ(ifield_lowering_infos_.Size(), 0u); + ifield_lowering_infos_.Resize(ifield_pos); + for (size_t pos = 0u; pos != ifield_pos; ++pos) { + ifield_lowering_infos_.Insert(MirIFieldLoweringInfo(field_idxs[pos])); + } + MirIFieldLoweringInfo::Resolve(cu_->compiler_driver, GetCurrentDexCompilationUnit(), + ifield_lowering_infos_.GetRawStorage(), ifield_pos); + } + + if (sfield_pos != max_refs) { + // Resolve static field infos. + DCHECK_EQ(sfield_lowering_infos_.Size(), 0u); + sfield_lowering_infos_.Resize(max_refs - sfield_pos); + for (size_t pos = max_refs; pos != sfield_pos;) { + --pos; + sfield_lowering_infos_.Insert(MirSFieldLoweringInfo(field_idxs[pos])); + } + MirSFieldLoweringInfo::Resolve(cu_->compiler_driver, GetCurrentDexCompilationUnit(), + sfield_lowering_infos_.GetRawStorage(), max_refs - sfield_pos); + } +} + +void MIRGraph::DoCacheMethodLoweringInfo() { + static constexpr uint16_t invoke_types[] = { kVirtual, kSuper, kDirect, kStatic, kInterface }; + + // Embed the map value in the entry to avoid extra padding in 64-bit builds. + struct MapEntry { + // Map key: target_method_idx, invoke_type, devirt_target. Ordered to avoid padding. + const MethodReference* devirt_target; + uint16_t target_method_idx; + uint16_t invoke_type; + // Map value. + uint32_t lowering_info_index; + }; + + // Sort INVOKEs by method index, then by opcode, then by devirtualization target. + struct MapEntryComparator { + bool operator()(const MapEntry& lhs, const MapEntry& rhs) const { + if (lhs.target_method_idx != rhs.target_method_idx) { + return lhs.target_method_idx < rhs.target_method_idx; + } + if (lhs.invoke_type != rhs.invoke_type) { + return lhs.invoke_type < rhs.invoke_type; + } + if (lhs.devirt_target != rhs.devirt_target) { + if (lhs.devirt_target == nullptr) { + return true; + } + if (rhs.devirt_target == nullptr) { + return false; + } + return devirt_cmp(*lhs.devirt_target, *rhs.devirt_target); + } + return false; + } + MethodReferenceComparator devirt_cmp; + }; + + ScopedArenaAllocator allocator(&cu_->arena_stack); + + // All INVOKE instructions take 3 code units and there must also be a RETURN. + uint32_t max_refs = (current_code_item_->insns_size_in_code_units_ - 1u) / 3u; + + // Map invoke key (see MapEntry) to lowering info index and vice versa. + // The invoke_map and sequential entries are essentially equivalent to Boost.MultiIndex's + // multi_index_container with one ordered index and one sequential index. + ScopedArenaSet invoke_map(MapEntryComparator(), + allocator.Adapter()); + const MapEntry** sequential_entries = reinterpret_cast( + allocator.Alloc(max_refs * sizeof(sequential_entries[0]), kArenaAllocMisc)); + + // Find INVOKE insns and their devirtualization targets. + AllNodesIterator iter(this); + for (BasicBlock* bb = iter.Next(); bb != nullptr; bb = iter.Next()) { + if (bb->block_type != kDalvikByteCode) { + continue; + } + for (MIR* mir = bb->first_mir_insn; mir != nullptr; mir = mir->next) { + if (mir->dalvikInsn.opcode >= Instruction::INVOKE_VIRTUAL && + mir->dalvikInsn.opcode <= Instruction::INVOKE_INTERFACE_RANGE && + mir->dalvikInsn.opcode != Instruction::RETURN_VOID_BARRIER) { + // Decode target method index and invoke type. + const Instruction* insn = Instruction::At(current_code_item_->insns_ + mir->offset); + uint16_t target_method_idx; + uint16_t invoke_type_idx; + if (mir->dalvikInsn.opcode <= Instruction::INVOKE_INTERFACE) { + target_method_idx = insn->VRegB_35c(); + invoke_type_idx = mir->dalvikInsn.opcode - Instruction::INVOKE_VIRTUAL; + } else { + target_method_idx = insn->VRegB_3rc(); + invoke_type_idx = mir->dalvikInsn.opcode - Instruction::INVOKE_VIRTUAL_RANGE; + } + + // Find devirtualization target. + // TODO: The devirt map is ordered by the dex pc here. Is there a way to get INVOKEs + // ordered by dex pc as well? That would allow us to keep an iterator to devirt targets + // and increment it as needed instead of making O(log n) lookups. + const VerifiedMethod* verified_method = GetCurrentDexCompilationUnit()->GetVerifiedMethod(); + const MethodReference* devirt_target = verified_method->GetDevirtTarget(mir->offset); + + // Try to insert a new entry. If the insertion fails, we will have found an old one. + MapEntry entry = { + devirt_target, + target_method_idx, + invoke_types[invoke_type_idx], + static_cast(invoke_map.size()) + }; + auto it = invoke_map.insert(entry).first; // Iterator to either the old or the new entry. + mir->meta.method_lowering_info = it->lowering_info_index; + // If we didn't actually insert, this will just overwrite an existing value with the same. + sequential_entries[it->lowering_info_index] = &*it; + } + } + } + + if (invoke_map.empty()) { + return; + } + + // Prepare unique method infos, set method info indexes for their MIRs. + DCHECK_EQ(method_lowering_infos_.Size(), 0u); + const size_t count = invoke_map.size(); + method_lowering_infos_.Resize(count); + for (size_t pos = 0u; pos != count; ++pos) { + const MapEntry* entry = sequential_entries[pos]; + MirMethodLoweringInfo method_info(entry->target_method_idx, + static_cast(entry->invoke_type)); + if (entry->devirt_target != nullptr) { + method_info.SetDevirtualizationTarget(*entry->devirt_target); + } + method_lowering_infos_.Insert(method_info); + } + MirMethodLoweringInfo::Resolve(cu_->compiler_driver, GetCurrentDexCompilationUnit(), + method_lowering_infos_.GetRawStorage(), count); +} + +bool MIRGraph::SkipCompilationByName(const std::string& methodname) { + return cu_->compiler_driver->SkipCompilation(methodname); } } // namespace art diff --git a/compiler/dex/mir_dataflow.cc b/compiler/dex/mir_dataflow.cc index 3a73717a7b0..6512bb9346e 100644 --- a/compiler/dex/mir_dataflow.cc +++ b/compiler/dex/mir_dataflow.cc @@ -29,7 +29,7 @@ namespace art { * 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] = { +const uint64_t MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { // 00 NOP DF_NOP, @@ -235,130 +235,130 @@ const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { 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, + DF_DA | DF_UB | DF_UC | DF_NULL_CHK_0 | DF_RANGE_CHK_1 | DF_REF_B | DF_CORE_C | DF_LVN, // 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, + DF_DA | DF_A_WIDE | DF_UB | DF_UC | DF_NULL_CHK_0 | DF_RANGE_CHK_1 | DF_REF_B | DF_CORE_C | DF_LVN, // 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, + DF_DA | DF_UB | DF_UC | DF_NULL_CHK_0 | DF_RANGE_CHK_1 | DF_REF_A | DF_REF_B | DF_CORE_C | DF_LVN, // 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, + DF_DA | DF_UB | DF_UC | DF_NULL_CHK_0 | DF_RANGE_CHK_1 | DF_REF_B | DF_CORE_C | DF_LVN, // 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, + DF_DA | DF_UB | DF_UC | DF_NULL_CHK_0 | DF_RANGE_CHK_1 | DF_REF_B | DF_CORE_C | DF_LVN, // 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, + DF_DA | DF_UB | DF_UC | DF_NULL_CHK_0 | DF_RANGE_CHK_1 | DF_REF_B | DF_CORE_C | DF_LVN, // 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, + DF_DA | DF_UB | DF_UC | DF_NULL_CHK_0 | DF_RANGE_CHK_1 | DF_REF_B | DF_CORE_C | DF_LVN, // 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, + DF_UA | DF_UB | DF_UC | DF_NULL_CHK_1 | DF_RANGE_CHK_2 | DF_REF_B | DF_CORE_C | DF_LVN, // 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, + DF_UA | DF_A_WIDE | DF_UB | DF_UC | DF_NULL_CHK_2 | DF_RANGE_CHK_3 | DF_REF_B | DF_CORE_C | DF_LVN, // 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, + DF_UA | DF_UB | DF_UC | DF_NULL_CHK_1 | DF_RANGE_CHK_2 | DF_REF_A | DF_REF_B | DF_CORE_C | DF_LVN, // 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, + DF_UA | DF_UB | DF_UC | DF_NULL_CHK_1 | DF_RANGE_CHK_2 | DF_REF_B | DF_CORE_C | DF_LVN, // 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, + DF_UA | DF_UB | DF_UC | DF_NULL_CHK_1 | DF_RANGE_CHK_2 | DF_REF_B | DF_CORE_C | DF_LVN, // 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, + DF_UA | DF_UB | DF_UC | DF_NULL_CHK_1 | DF_RANGE_CHK_2 | DF_REF_B | DF_CORE_C | DF_LVN, // 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, + DF_UA | DF_UB | DF_UC | DF_NULL_CHK_1 | DF_RANGE_CHK_2 | DF_REF_B | DF_CORE_C | DF_LVN, // 52 IGET vA, vB, field@CCCC - DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B | DF_IFIELD | DF_LVN, // 53 IGET_WIDE vA, vB, field@CCCC - DF_DA | DF_A_WIDE | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + DF_DA | DF_A_WIDE | DF_UB | DF_NULL_CHK_0 | DF_REF_B | DF_IFIELD | DF_LVN, // 54 IGET_OBJECT vA, vB, field@CCCC - DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_A | DF_REF_B, + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_A | DF_REF_B | DF_IFIELD | DF_LVN, // 55 IGET_BOOLEAN vA, vB, field@CCCC - DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B | DF_IFIELD | DF_LVN, // 56 IGET_BYTE vA, vB, field@CCCC - DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B | DF_IFIELD | DF_LVN, // 57 IGET_CHAR vA, vB, field@CCCC - DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B | DF_IFIELD | DF_LVN, // 58 IGET_SHORT vA, vB, field@CCCC - DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B | DF_IFIELD | DF_LVN, // 59 IPUT vA, vB, field@CCCC - DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B, + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B | DF_IFIELD | DF_LVN, // 5A IPUT_WIDE vA, vB, field@CCCC - DF_UA | DF_A_WIDE | DF_UB | DF_NULL_CHK_2 | DF_REF_B, + DF_UA | DF_A_WIDE | DF_UB | DF_NULL_CHK_2 | DF_REF_B | DF_IFIELD | DF_LVN, // 5B IPUT_OBJECT vA, vB, field@CCCC - DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_A | DF_REF_B, + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_A | DF_REF_B | DF_IFIELD | DF_LVN, // 5C IPUT_BOOLEAN vA, vB, field@CCCC - DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B, + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B | DF_IFIELD | DF_LVN, // 5D IPUT_BYTE vA, vB, field@CCCC - DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B, + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B | DF_IFIELD | DF_LVN, // 5E IPUT_CHAR vA, vB, field@CCCC - DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B, + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B | DF_IFIELD | DF_LVN, // 5F IPUT_SHORT vA, vB, field@CCCC - DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B, + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B | DF_IFIELD | DF_LVN, // 60 SGET vAA, field@BBBB - DF_DA | DF_UMS, + DF_DA | DF_SFIELD | DF_UMS, // 61 SGET_WIDE vAA, field@BBBB - DF_DA | DF_A_WIDE | DF_UMS, + DF_DA | DF_A_WIDE | DF_SFIELD | DF_UMS, // 62 SGET_OBJECT vAA, field@BBBB - DF_DA | DF_REF_A | DF_UMS, + DF_DA | DF_REF_A | DF_SFIELD | DF_UMS, // 63 SGET_BOOLEAN vAA, field@BBBB - DF_DA | DF_UMS, + DF_DA | DF_SFIELD | DF_UMS, // 64 SGET_BYTE vAA, field@BBBB - DF_DA | DF_UMS, + DF_DA | DF_SFIELD | DF_UMS, // 65 SGET_CHAR vAA, field@BBBB - DF_DA | DF_UMS, + DF_DA | DF_SFIELD | DF_UMS, // 66 SGET_SHORT vAA, field@BBBB - DF_DA | DF_UMS, + DF_DA | DF_SFIELD | DF_UMS, // 67 SPUT vAA, field@BBBB - DF_UA | DF_UMS, + DF_UA | DF_SFIELD | DF_UMS, // 68 SPUT_WIDE vAA, field@BBBB - DF_UA | DF_A_WIDE | DF_UMS, + DF_UA | DF_A_WIDE | DF_SFIELD | DF_UMS, // 69 SPUT_OBJECT vAA, field@BBBB - DF_UA | DF_REF_A | DF_UMS, + DF_UA | DF_REF_A | DF_SFIELD | DF_UMS, // 6A SPUT_BOOLEAN vAA, field@BBBB - DF_UA | DF_UMS, + DF_UA | DF_SFIELD | DF_UMS, // 6B SPUT_BYTE vAA, field@BBBB - DF_UA | DF_UMS, + DF_UA | DF_SFIELD | DF_UMS, // 6C SPUT_CHAR vAA, field@BBBB - DF_UA | DF_UMS, + DF_UA | DF_SFIELD | DF_UMS, // 6D SPUT_SHORT vAA, field@BBBB - DF_UA | DF_UMS, + DF_UA | DF_SFIELD | DF_UMS, // 6E INVOKE_VIRTUAL {vD, vE, vF, vG, vA} DF_FORMAT_35C | DF_NULL_CHK_OUT0 | DF_UMS, @@ -375,7 +375,7 @@ const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { // 72 INVOKE_INTERFACE {vD, vE, vF, vG, vA} DF_FORMAT_35C | DF_NULL_CHK_OUT0 | DF_UMS, - // 73 UNUSED_73 + // 73 RETURN_VOID_BARRIER DF_NOP, // 74 INVOKE_VIRTUAL_RANGE {vCCCC .. vNNNN} @@ -472,10 +472,10 @@ const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { 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, + DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C | DF_ZERO_DIV_CHECK, // 94 REM_INT vAA, vBB, vCC - DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, + DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C | DF_ZERO_DIV_CHECK, // 95 AND_INT vAA, vBB, vCC DF_DA | DF_UB | DF_UC | DF_CORE_A | DF_CORE_B | DF_CORE_C, @@ -505,10 +505,10 @@ const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { 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, + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_CORE_A | DF_CORE_B | DF_CORE_C | DF_ZERO_DIV_CHECK, // 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, + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_CORE_A | DF_CORE_B | DF_CORE_C | DF_ZERO_DIV_CHECK, // 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, @@ -538,10 +538,10 @@ const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { 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, + DF_DA | DF_UB | DF_UC | DF_FP_A | DF_FP_B | DF_FP_C | DF_ZERO_DIV_CHECK, // AA REM_FLOAT vAA, vBB, vCC - DF_DA | DF_UB | DF_UC | DF_FP_A | DF_FP_B | DF_FP_C, + DF_DA | DF_UB | DF_UC | DF_FP_A | DF_FP_B | DF_FP_C | DF_ZERO_DIV_CHECK, // 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, @@ -553,10 +553,10 @@ const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { 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, + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_FP_A | DF_FP_B | DF_FP_C | DF_ZERO_DIV_CHECK, // 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, + DF_DA | DF_A_WIDE | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_FP_A | DF_FP_B | DF_FP_C | DF_ZERO_DIV_CHECK, // B0 ADD_INT_2ADDR vA, vB DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, @@ -568,10 +568,10 @@ const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { 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, + DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B | DF_ZERO_DIV_CHECK, // B4 REM_INT_2ADDR vA, vB - DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, + DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B | DF_ZERO_DIV_CHECK, // B5 AND_INT_2ADDR vA, vB DF_DA | DF_UA | DF_UB | DF_CORE_A | DF_CORE_B, @@ -601,10 +601,10 @@ const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { 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, + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B | DF_ZERO_DIV_CHECK, // BF REM_LONG_2ADDR vA, vB - DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B, + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B | DF_ZERO_DIV_CHECK, // C0 AND_LONG_2ADDR vA, vB DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_CORE_A | DF_CORE_B, @@ -634,10 +634,10 @@ const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { 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, + DF_DA | DF_UA | DF_UB | DF_FP_A | DF_FP_B | DF_ZERO_DIV_CHECK, // CA REM_FLOAT_2ADDR vA, vB - DF_DA | DF_UA | DF_UB | DF_FP_A | DF_FP_B, + DF_DA | DF_UA | DF_UB | DF_FP_A | DF_FP_B | DF_ZERO_DIV_CHECK, // CB ADD_DOUBLE_2ADDR vA, vB DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_FP_A | DF_FP_B, @@ -649,10 +649,10 @@ const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { 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, + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_FP_A | DF_FP_B | DF_ZERO_DIV_CHECK, // CF REM_DOUBLE_2ADDR vA, vB - DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_FP_A | DF_FP_B, + DF_DA | DF_A_WIDE | DF_UA | DF_UB | DF_B_WIDE | DF_FP_A | DF_FP_B | DF_ZERO_DIV_CHECK, // D0 ADD_INT_LIT16 vA, vB, #+CCCC DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, @@ -712,31 +712,31 @@ const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { DF_DA | DF_UB | DF_CORE_A | DF_CORE_B, // E3 IGET_VOLATILE - DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_B | DF_IFIELD | DF_LVN, // E4 IPUT_VOLATILE - DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B, + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_B | DF_IFIELD | DF_LVN, // E5 SGET_VOLATILE - DF_DA | DF_UMS, + DF_DA | DF_SFIELD | DF_UMS, // E6 SPUT_VOLATILE - DF_UA | DF_UMS, + DF_UA | DF_SFIELD | DF_UMS, // E7 IGET_OBJECT_VOLATILE - DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_A | DF_REF_B, + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_REF_A | DF_REF_B | DF_IFIELD | DF_LVN, // E8 IGET_WIDE_VOLATILE - DF_DA | DF_A_WIDE | DF_UB | DF_NULL_CHK_0 | DF_REF_B, + DF_DA | DF_A_WIDE | DF_UB | DF_NULL_CHK_0 | DF_REF_B | DF_IFIELD | DF_LVN, // E9 IPUT_WIDE_VOLATILE - DF_UA | DF_A_WIDE | DF_UB | DF_NULL_CHK_2 | DF_REF_B, + DF_UA | DF_A_WIDE | DF_UB | DF_NULL_CHK_2 | DF_REF_B | DF_IFIELD | DF_LVN, // EA SGET_WIDE_VOLATILE - DF_DA | DF_A_WIDE | DF_UMS, + DF_DA | DF_A_WIDE | DF_SFIELD | DF_UMS, // EB SPUT_WIDE_VOLATILE - DF_UA | DF_A_WIDE | DF_UMS, + DF_UA | DF_A_WIDE | DF_SFIELD | DF_UMS, // EC BREAKPOINT DF_NOP, @@ -757,22 +757,22 @@ const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { DF_NOP, // F2 IGET_QUICK - DF_DA | DF_UB | DF_NULL_CHK_0, + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_IFIELD | DF_LVN, // F3 IGET_WIDE_QUICK - DF_DA | DF_A_WIDE | DF_UB | DF_NULL_CHK_0, + DF_DA | DF_A_WIDE | DF_UB | DF_NULL_CHK_0 | DF_IFIELD | DF_LVN, // F4 IGET_OBJECT_QUICK - DF_DA | DF_UB | DF_NULL_CHK_0, + DF_DA | DF_UB | DF_NULL_CHK_0 | DF_IFIELD | DF_LVN, // F5 IPUT_QUICK - DF_UA | DF_UB | DF_NULL_CHK_1, + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_IFIELD | DF_LVN, // F6 IPUT_WIDE_QUICK - DF_UA | DF_A_WIDE | DF_UB | DF_NULL_CHK_2, + DF_UA | DF_A_WIDE | DF_UB | DF_NULL_CHK_2 | DF_IFIELD | DF_LVN, // F7 IPUT_OBJECT_QUICK - DF_UA | DF_UB | DF_NULL_CHK_1, + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_IFIELD | DF_LVN, // F8 INVOKE_VIRTUAL_QUICK DF_FORMAT_35C | DF_NULL_CHK_OUT0 | DF_UMS, @@ -787,13 +787,13 @@ const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { 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, + DF_UA | DF_UB | DF_NULL_CHK_1 | DF_REF_A | DF_REF_B | DF_IFIELD | DF_LVN, // FD SGET_OBJECT_VOLATILE - DF_DA | DF_REF_A | DF_UMS, + DF_DA | DF_REF_A | DF_SFIELD | DF_UMS, // FE SPUT_OBJECT_VOLATILE - DF_UA | DF_REF_A | DF_UMS, + DF_UA | DF_REF_A | DF_SFIELD | DF_UMS, // FF UNUSED_FF DF_NOP, @@ -840,6 +840,54 @@ const int MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { // 113 MIR_SELECT DF_DA | DF_UB, + + // 114 MirOpConstVector + DF_DA, + + // 115 MirOpMoveVector + 0, + + // 116 MirOpPackedMultiply + 0, + + // 117 MirOpPackedAddition + 0, + + // 118 MirOpPackedSubtract + 0, + + // 119 MirOpPackedShiftLeft + 0, + + // 120 MirOpPackedSignedShiftRight + 0, + + // 121 MirOpPackedUnsignedShiftRight + 0, + + // 122 MirOpPackedAnd + 0, + + // 123 MirOpPackedOr + 0, + + // 124 MirOpPackedXor + 0, + + // 125 MirOpPackedAddReduce + DF_DA | DF_UA, + + // 126 MirOpPackedReduce + DF_DA, + + // 127 MirOpPackedSet + DF_UB, + + // 128 MirOpReserveVectorRegisters + 0, + + // 129 MirOpReturnVectorRegisters + 0, }; /* Return the base virtual register for a SSA name */ @@ -861,6 +909,16 @@ void MIRGraph::HandleDef(ArenaBitVector* def_v, int dalvik_reg_id) { def_v->SetBit(dalvik_reg_id); } +void MIRGraph::HandleExtended(ArenaBitVector* use_v, ArenaBitVector* def_v, + ArenaBitVector* live_in_v, + const MIR::DecodedInstruction& d_insn) { + switch (static_cast(d_insn.opcode)) { + default: + LOG(ERROR) << "Unexpected Extended Opcode " << d_insn.opcode; + break; + } +} + /* * 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. @@ -879,8 +937,8 @@ bool MIRGraph::FindLocalLiveIn(BasicBlock* bb) { 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; + uint64_t df_attributes = GetDataFlowAttributes(mir); + MIR::DecodedInstruction* d_insn = &mir->dalvikInsn; if (df_attributes & DF_HAS_USES) { if (df_attributes & DF_UA) { @@ -918,6 +976,9 @@ bool MIRGraph::FindLocalLiveIn(BasicBlock* bb) { HandleDef(def_v, d_insn->vA+1); } } + if (df_attributes & DF_FORMAT_EXTENDED) { + HandleExtended(use_v, def_v, live_in_v, mir->dalvikInsn); + } } return true; } @@ -925,11 +986,17 @@ bool MIRGraph::FindLocalLiveIn(BasicBlock* bb) { 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(); + uint32_t ssa_reg = GetNumSSARegs(); SetNumSSARegs(ssa_reg + 1); ssa_base_vregs_->Insert(v_reg); ssa_subscripts_->Insert(subscript); DCHECK_EQ(ssa_base_vregs_->Size(), ssa_subscripts_->Size()); + // If we are expanding very late, update use counts too. + if (ssa_reg > 0 && use_counts_.Size() == ssa_reg) { + // Need to expand the counts. + use_counts_.Insert(0); + raw_use_counts_.Insert(0); + } return ssa_reg; } @@ -947,18 +1014,34 @@ void MIRGraph::HandleSSADef(int* defs, int dalvik_reg, int reg_index) { defs[reg_index] = ssa_reg; } +void MIRGraph::AllocateSSAUseData(MIR *mir, int num_uses) { + mir->ssa_rep->num_uses = num_uses; + + if (mir->ssa_rep->num_uses_allocated < num_uses) { + mir->ssa_rep->uses = static_cast(arena_->Alloc(sizeof(int) * num_uses, kArenaAllocDFInfo)); + // NOTE: will be filled in during type & size inference pass + mir->ssa_rep->fp_use = static_cast(arena_->Alloc(sizeof(bool) * num_uses, kArenaAllocDFInfo)); + } +} + +void MIRGraph::AllocateSSADefData(MIR *mir, int num_defs) { + mir->ssa_rep->num_defs = num_defs; + + if (mir->ssa_rep->num_defs_allocated < num_defs) { + mir->ssa_rep->defs = static_cast(arena_->Alloc(sizeof(int) * num_defs, + kArenaAllocDFInfo)); + mir->ssa_rep->fp_def = static_cast(arena_->Alloc(sizeof(bool) * num_defs, + kArenaAllocDFInfo)); + } +} + /* Look up new SSA names for format_35c instructions */ void MIRGraph::DataFlowSSAFormat35C(MIR* mir) { - DecodedInstruction *d_insn = &mir->dalvikInsn; + 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_->Alloc(sizeof(int) * num_uses, - ArenaAllocator::kAllocDFInfo)); - // NOTE: will be filled in during type & size inference pass - mir->ssa_rep->fp_use = static_cast(arena_->Alloc(sizeof(bool) * num_uses, - ArenaAllocator::kAllocDFInfo)); + AllocateSSAUseData(mir, num_uses); for (i = 0; i < num_uses; i++) { HandleSSAUse(mir->ssa_rep->uses, d_insn->arg[i], i); @@ -967,22 +1050,25 @@ void MIRGraph::DataFlowSSAFormat35C(MIR* mir) { /* Look up new SSA names for format_3rc instructions */ void MIRGraph::DataFlowSSAFormat3RC(MIR* mir) { - DecodedInstruction *d_insn = &mir->dalvikInsn; + 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_->Alloc(sizeof(int) * num_uses, - ArenaAllocator::kAllocDFInfo)); - // NOTE: will be filled in during type & size inference pass - mir->ssa_rep->fp_use = static_cast(arena_->Alloc(sizeof(bool) * num_uses, - ArenaAllocator::kAllocDFInfo)); + AllocateSSAUseData(mir, num_uses); for (i = 0; i < num_uses; i++) { HandleSSAUse(mir->ssa_rep->uses, d_insn->vC+i, i); } } +void MIRGraph::DataFlowSSAFormatExtended(MIR* mir) { + switch (static_cast(mir->dalvikInsn.opcode)) { + default: + LOG(ERROR) << "Missing case for extended MIR: " << mir->dalvikInsn.opcode; + break; + } +} + /* Entry function to convert a block into SSA representation */ bool MIRGraph::DoSSAConversion(BasicBlock* bb) { MIR* mir; @@ -992,16 +1078,16 @@ bool MIRGraph::DoSSAConversion(BasicBlock* bb) { for (mir = bb->first_mir_insn; mir != NULL; mir = mir->next) { mir->ssa_rep = static_cast(arena_->Alloc(sizeof(SSARepresentation), - ArenaAllocator::kAllocDFInfo)); + kArenaAllocDFInfo)); + memset(mir->ssa_rep, 0, sizeof(*mir->ssa_rep)); - int df_attributes = oat_data_flow_attributes_[mir->dalvikInsn.opcode]; + uint64_t df_attributes = GetDataFlowAttributes(mir); // If not a pseudo-op, note non-leaf or can throw - if (static_cast(mir->dalvikInsn.opcode) < - static_cast(kNumPackedOpcodes)) { + if (!MIR::DecodedInstruction::IsPseudoMirOp(mir->dalvikInsn.opcode)) { int flags = Instruction::FlagsOf(mir->dalvikInsn.opcode); - if (flags & Instruction::kInvoke) { + if ((flags & Instruction::kInvoke) != 0 && (mir->optimization_flags & MIR_INLINED) == 0) { attributes_ &= ~METHOD_IS_LEAF; } } @@ -1018,6 +1104,11 @@ bool MIRGraph::DoSSAConversion(BasicBlock* bb) { continue; } + if (df_attributes & DF_FORMAT_EXTENDED) { + DataFlowSSAFormatExtended(mir); + continue; + } + if (df_attributes & DF_HAS_USES) { if (df_attributes & DF_UA) { num_uses++; @@ -1039,13 +1130,7 @@ bool MIRGraph::DoSSAConversion(BasicBlock* bb) { } } - if (num_uses) { - mir->ssa_rep->num_uses = num_uses; - mir->ssa_rep->uses = static_cast(arena_->Alloc(sizeof(int) * num_uses, - ArenaAllocator::kAllocDFInfo)); - mir->ssa_rep->fp_use = static_cast(arena_->Alloc(sizeof(bool) * num_uses, - ArenaAllocator::kAllocDFInfo)); - } + AllocateSSAUseData(mir, num_uses); int num_defs = 0; @@ -1056,15 +1141,9 @@ bool MIRGraph::DoSSAConversion(BasicBlock* bb) { } } - if (num_defs) { - mir->ssa_rep->num_defs = num_defs; - mir->ssa_rep->defs = static_cast(arena_->Alloc(sizeof(int) * num_defs, - ArenaAllocator::kAllocDFInfo)); - mir->ssa_rep->fp_def = static_cast(arena_->Alloc(sizeof(bool) * num_defs, - ArenaAllocator::kAllocDFInfo)); - } + AllocateSSADefData(mir, num_defs); - DecodedInstruction *d_insn = &mir->dalvikInsn; + MIR::DecodedInstruction* d_insn = &mir->dalvikInsn; if (df_attributes & DF_HAS_USES) { num_uses = 0; @@ -1108,11 +1187,11 @@ bool MIRGraph::DoSSAConversion(BasicBlock* bb) { * input to PHI nodes can be derived from the snapshot of all * predecessor blocks. */ - bb->data_flow_info->vreg_to_ssa_map = + bb->data_flow_info->vreg_to_ssa_map_exit = static_cast(arena_->Alloc(sizeof(int) * cu_->num_dalvik_registers, - ArenaAllocator::kAllocDFInfo)); + kArenaAllocDFInfo)); - memcpy(bb->data_flow_info->vreg_to_ssa_map, vreg_to_ssa_map_, + memcpy(bb->data_flow_info->vreg_to_ssa_map_exit, vreg_to_ssa_map_, sizeof(int) * cu_->num_dalvik_registers); return true; } @@ -1147,19 +1226,19 @@ void MIRGraph::CompilerInitializeSSAConversion() { */ vreg_to_ssa_map_ = static_cast(arena_->Alloc(sizeof(int) * num_dalvik_reg, - ArenaAllocator::kAllocDFInfo)); + kArenaAllocDFInfo)); /* Keep track of the higest def for each dalvik reg */ ssa_last_defs_ = static_cast(arena_->Alloc(sizeof(int) * num_dalvik_reg, - ArenaAllocator::kAllocDFInfo)); + kArenaAllocDFInfo)); 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); + // Create a compiler temporary for Method*. This is done after SSA initialization. + GetNewCompilerTemp(kCompilerTempSpecialMethodPtr, false); /* * Allocate the BasicBlockDataFlow structure for the entry and code blocks @@ -1175,7 +1254,7 @@ void MIRGraph::CompilerInitializeSSAConversion() { bb->block_type == kExitBlock) { bb->data_flow_info = static_cast(arena_->Alloc(sizeof(BasicBlockDataFlow), - ArenaAllocator::kAllocDFInfo)); + kArenaAllocDFInfo)); } } } @@ -1221,10 +1300,10 @@ bool MIRGraph::InvokeUsesMethodStar(MIR* mir) { uint32_t current_offset = static_cast(current_offset_); bool fast_path = cu_->compiler_driver->ComputeInvokeInfo(&m_unit, current_offset, - type, target_method, - vtable_idx, - direct_code, direct_method, - false) && + false, true, + &type, &target_method, + &vtable_idx, + &direct_code, &direct_method) && !(cu_->enable_debug & (1 << kDebugSlowInvokePath)); return (((type == kDirect) || (type == kStatic)) && fast_path && ((direct_code == 0) || (direct_method == 0))); @@ -1235,83 +1314,61 @@ bool MIRGraph::InvokeUsesMethodStar(MIR* mir) { * 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) { +void MIRGraph::CountUses(struct BasicBlock* bb) { if (bb->block_type != kDalvikByteCode) { - return false; + return; } + // Each level of nesting adds *100 to count, up to 3 levels deep. + uint32_t depth = std::min(3U, static_cast(bb->nesting_depth)); + uint32_t weight = std::max(1U, depth * 100); 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)); + use_counts_.Put(s_reg, use_counts_.Get(s_reg) + weight); } if (!(cu_->disable_opt & (1 << kPromoteCompilerTemps))) { - int df_attributes = oat_data_flow_attributes_[mir->dalvikInsn.opcode]; + uint64_t df_attributes = GetDataFlowAttributes(mir); // 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 + * for Method* on invoke-class opcodes. This is a relatively expensive + * operation, so should only be done once. + * TODO: refactor InvokeUsesMethodStar() to perform check at parse time, + * and save results for both here and GenInvoke. For now, go ahead + * and assume all invokes use method*. */ - 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)); - } + raw_use_counts_.Increment(method_sreg_); + use_counts_.Put(method_sreg_, use_counts_.Get(method_sreg_) + 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); + GrowableArray::Iterator iter(bb->predecessors); while (true) { - BasicBlock *pred_bb = iter.Next(); + BasicBlock* pred_bb = GetBasicBlock(iter.Next()); if (!pred_bb) break; bool found = false; - if (pred_bb->taken == bb) { + if (pred_bb->taken == bb->id) { found = true; - } else if (pred_bb->fall_through == bb) { + } else if (pred_bb->fall_through == bb->id) { found = true; - } else if (pred_bb->successor_block_list.block_list_type != kNotUsed) { - GrowableArray::Iterator iterator(pred_bb->successor_block_list.blocks); + } else if (pred_bb->successor_block_list_type != kNotUsed) { + GrowableArray::Iterator iterator(pred_bb->successor_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) { + BasicBlockId succ_bb = successor_block_info->block; + if (succ_bb == bb->id) { found = true; break; } @@ -1331,7 +1388,7 @@ bool MIRGraph::VerifyPredInfo(BasicBlock* bb) { void MIRGraph::VerifyDataflow() { /* Verify if all blocks are connected as claimed */ - AllNodesIterator iter(this, false /* not iterative */); + AllNodesIterator iter(this); for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { VerifyPredInfo(bb); } diff --git a/compiler/dex/mir_field_info.cc b/compiler/dex/mir_field_info.cc new file mode 100644 index 00000000000..68247b78075 --- /dev/null +++ b/compiler/dex/mir_field_info.cc @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mir_field_info.h" + +#include + +#include "base/logging.h" +#include "driver/compiler_driver.h" +#include "driver/compiler_driver-inl.h" +#include "mirror/class_loader.h" // Only to allow casts in Handle. +#include "mirror/dex_cache.h" // Only to allow casts in Handle. +#include "scoped_thread_state_change.h" +#include "handle_scope-inl.h" + +namespace art { + +void MirIFieldLoweringInfo::Resolve(CompilerDriver* compiler_driver, + const DexCompilationUnit* mUnit, + MirIFieldLoweringInfo* field_infos, size_t count) { + if (kIsDebugBuild) { + DCHECK(field_infos != nullptr); + DCHECK_NE(count, 0u); + for (auto it = field_infos, end = field_infos + count; it != end; ++it) { + MirIFieldLoweringInfo unresolved(it->field_idx_); + DCHECK_EQ(memcmp(&unresolved, &*it, sizeof(*it)), 0); + } + } + + // We're going to resolve fields and check access in a tight loop. It's better to hold + // the lock and needed references once than re-acquiring them again and again. + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<3> hs(soa.Self()); + Handle dex_cache(hs.NewHandle(compiler_driver->GetDexCache(mUnit))); + Handle class_loader( + hs.NewHandle(compiler_driver->GetClassLoader(soa, mUnit))); + Handle referrer_class(hs.NewHandle( + compiler_driver->ResolveCompilingMethodsClass(soa, dex_cache, class_loader, mUnit))); + // Even if the referrer class is unresolved (i.e. we're compiling a method without class + // definition) we still want to resolve fields and record all available info. + + for (auto it = field_infos, end = field_infos + count; it != end; ++it) { + uint32_t field_idx = it->field_idx_; + mirror::ArtField* resolved_field = + compiler_driver->ResolveField(soa, dex_cache, class_loader, mUnit, field_idx, false); + if (UNLIKELY(resolved_field == nullptr)) { + continue; + } + compiler_driver->GetResolvedFieldDexFileLocation(resolved_field, + &it->declaring_dex_file_, &it->declaring_class_idx_, &it->declaring_field_idx_); + bool is_volatile = compiler_driver->IsFieldVolatile(resolved_field); + it->field_offset_ = resolved_field->GetOffset(); + std::pair fast_path = compiler_driver->IsFastInstanceField( + dex_cache.Get(), referrer_class.Get(), resolved_field, field_idx); + it->flags_ = 0u | // Without kFlagIsStatic. + (is_volatile ? kFlagIsVolatile : 0u) | + (fast_path.first ? kFlagFastGet : 0u) | + (fast_path.second ? kFlagFastPut : 0u); + } +} + +void MirSFieldLoweringInfo::Resolve(CompilerDriver* compiler_driver, + const DexCompilationUnit* mUnit, + MirSFieldLoweringInfo* field_infos, size_t count) { + if (kIsDebugBuild) { + DCHECK(field_infos != nullptr); + DCHECK_NE(count, 0u); + for (auto it = field_infos, end = field_infos + count; it != end; ++it) { + MirSFieldLoweringInfo unresolved(it->field_idx_); + // In 64-bit builds, there's padding after storage_index_, don't include it in memcmp. + size_t size = OFFSETOF_MEMBER(MirSFieldLoweringInfo, storage_index_) + + sizeof(it->storage_index_); + DCHECK_EQ(memcmp(&unresolved, &*it, size), 0); + } + } + + // We're going to resolve fields and check access in a tight loop. It's better to hold + // the lock and needed references once than re-acquiring them again and again. + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<3> hs(soa.Self()); + Handle dex_cache(hs.NewHandle(compiler_driver->GetDexCache(mUnit))); + Handle class_loader( + hs.NewHandle(compiler_driver->GetClassLoader(soa, mUnit))); + Handle referrer_class(hs.NewHandle( + compiler_driver->ResolveCompilingMethodsClass(soa, dex_cache, class_loader, mUnit))); + // Even if the referrer class is unresolved (i.e. we're compiling a method without class + // definition) we still want to resolve fields and record all available info. + + for (auto it = field_infos, end = field_infos + count; it != end; ++it) { + uint32_t field_idx = it->field_idx_; + mirror::ArtField* resolved_field = + compiler_driver->ResolveField(soa, dex_cache, class_loader, mUnit, field_idx, true); + if (UNLIKELY(resolved_field == nullptr)) { + continue; + } + compiler_driver->GetResolvedFieldDexFileLocation(resolved_field, + &it->declaring_dex_file_, &it->declaring_class_idx_, &it->declaring_field_idx_); + bool is_volatile = compiler_driver->IsFieldVolatile(resolved_field) ? 1u : 0u; + + bool is_referrers_class, is_initialized; + std::pair fast_path = compiler_driver->IsFastStaticField( + dex_cache.Get(), referrer_class.Get(), resolved_field, field_idx, &it->field_offset_, + &it->storage_index_, &is_referrers_class, &is_initialized); + it->flags_ = kFlagIsStatic | + (is_volatile ? kFlagIsVolatile : 0u) | + (fast_path.first ? kFlagFastGet : 0u) | + (fast_path.second ? kFlagFastPut : 0u) | + (is_referrers_class ? kFlagIsReferrersClass : 0u) | + (is_initialized ? kFlagIsInitialized : 0u); + } +} + +} // namespace art diff --git a/compiler/dex/mir_field_info.h b/compiler/dex/mir_field_info.h new file mode 100644 index 00000000000..9745c412c9a --- /dev/null +++ b/compiler/dex/mir_field_info.h @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_DEX_MIR_FIELD_INFO_H_ +#define ART_COMPILER_DEX_MIR_FIELD_INFO_H_ + +#include "base/macros.h" +#include "dex_file.h" +#include "offsets.h" + +namespace art { + +class CompilerDriver; +class DexCompilationUnit; + +/* + * Field info is calculated from the perspective of the compilation unit that accesses + * the field and stored in that unit's MIRGraph. Therefore it does not need to reference the + * dex file or method for which it has been calculated. However, we do store the declaring + * field index, class index and dex file of the resolved field to help distinguish between fields. + */ + +class MirFieldInfo { + public: + uint16_t FieldIndex() const { + return field_idx_; + } + + bool IsStatic() const { + return (flags_ & kFlagIsStatic) != 0u; + } + + bool IsResolved() const { + return declaring_dex_file_ != nullptr; + } + + const DexFile* DeclaringDexFile() const { + return declaring_dex_file_; + } + + uint16_t DeclaringClassIndex() const { + return declaring_class_idx_; + } + + uint16_t DeclaringFieldIndex() const { + return declaring_field_idx_; + } + + bool IsVolatile() const { + return (flags_ & kFlagIsVolatile) != 0u; + } + + protected: + enum { + kBitIsStatic = 0, + kBitIsVolatile, + kFieldInfoBitEnd + }; + static constexpr uint16_t kFlagIsVolatile = 1u << kBitIsVolatile; + static constexpr uint16_t kFlagIsStatic = 1u << kBitIsStatic; + + MirFieldInfo(uint16_t field_idx, uint16_t flags) + : field_idx_(field_idx), + flags_(flags), + declaring_field_idx_(0u), + declaring_class_idx_(0u), + declaring_dex_file_(nullptr) { + } + + // Make copy-ctor/assign/dtor protected to avoid slicing. + MirFieldInfo(const MirFieldInfo& other) = default; + MirFieldInfo& operator=(const MirFieldInfo& other) = default; + ~MirFieldInfo() = default; + + // The field index in the compiling method's dex file. + uint16_t field_idx_; + // Flags, for volatility and derived class data. + uint16_t flags_; + // The field index in the dex file that defines field, 0 if unresolved. + uint16_t declaring_field_idx_; + // The type index of the class declaring the field, 0 if unresolved. + uint16_t declaring_class_idx_; + // The dex file that defines the class containing the field and the field, nullptr if unresolved. + const DexFile* declaring_dex_file_; +}; + +class MirIFieldLoweringInfo : public MirFieldInfo { + public: + // For each requested instance field retrieve the field's declaring location (dex file, class + // index and field index) and volatility and compute whether we can fast path the access + // with IGET/IPUT. For fast path fields, retrieve the field offset. + static void Resolve(CompilerDriver* compiler_driver, const DexCompilationUnit* mUnit, + MirIFieldLoweringInfo* field_infos, size_t count) + LOCKS_EXCLUDED(Locks::mutator_lock_); + + // Construct an unresolved instance field lowering info. + explicit MirIFieldLoweringInfo(uint16_t field_idx) + : MirFieldInfo(field_idx, kFlagIsVolatile), // Without kFlagIsStatic. + field_offset_(0u) { + } + + bool FastGet() const { + return (flags_ & kFlagFastGet) != 0u; + } + + bool FastPut() const { + return (flags_ & kFlagFastPut) != 0u; + } + + MemberOffset FieldOffset() const { + return field_offset_; + } + + private: + enum { + kBitFastGet = kFieldInfoBitEnd, + kBitFastPut, + kIFieldLoweringInfoBitEnd + }; + COMPILE_ASSERT(kIFieldLoweringInfoBitEnd <= 16, too_many_flags); + static constexpr uint16_t kFlagFastGet = 1u << kBitFastGet; + static constexpr uint16_t kFlagFastPut = 1u << kBitFastPut; + + // The member offset of the field, 0u if unresolved. + MemberOffset field_offset_; + + friend class GlobalValueNumberingTest; + friend class LocalValueNumberingTest; +}; + +class MirSFieldLoweringInfo : public MirFieldInfo { + public: + // For each requested static field retrieve the field's declaring location (dex file, class + // index and field index) and volatility and compute whether we can fast path the access with + // IGET/IPUT. For fast path fields (at least for IGET), retrieve the information needed for + // the field access, i.e. the field offset, whether the field is in the same class as the + // method being compiled, whether the declaring class can be safely assumed to be initialized + // and the type index of the declaring class in the compiled method's dex file. + static void Resolve(CompilerDriver* compiler_driver, const DexCompilationUnit* mUnit, + MirSFieldLoweringInfo* field_infos, size_t count) + LOCKS_EXCLUDED(Locks::mutator_lock_); + + // Construct an unresolved static field lowering info. + explicit MirSFieldLoweringInfo(uint16_t field_idx) + : MirFieldInfo(field_idx, kFlagIsVolatile | kFlagIsStatic), + field_offset_(0u), + storage_index_(DexFile::kDexNoIndex) { + } + + bool FastGet() const { + return (flags_ & kFlagFastGet) != 0u; + } + + bool FastPut() const { + return (flags_ & kFlagFastPut) != 0u; + } + + bool IsReferrersClass() const { + return (flags_ & kFlagIsReferrersClass) != 0u; + } + + bool IsInitialized() const { + return (flags_ & kFlagIsInitialized) != 0u; + } + + MemberOffset FieldOffset() const { + return field_offset_; + } + + uint32_t StorageIndex() const { + return storage_index_; + } + + private: + enum { + kBitFastGet = kFieldInfoBitEnd, + kBitFastPut, + kBitIsReferrersClass, + kBitIsInitialized, + kSFieldLoweringInfoBitEnd + }; + COMPILE_ASSERT(kSFieldLoweringInfoBitEnd <= 16, too_many_flags); + static constexpr uint16_t kFlagFastGet = 1u << kBitFastGet; + static constexpr uint16_t kFlagFastPut = 1u << kBitFastPut; + static constexpr uint16_t kFlagIsReferrersClass = 1u << kBitIsReferrersClass; + static constexpr uint16_t kFlagIsInitialized = 1u << kBitIsInitialized; + + // The member offset of the field, 0u if unresolved. + MemberOffset field_offset_; + // The type index of the declaring class in the compiling method's dex file, + // -1 if the field is unresolved or there's no appropriate TypeId in that dex file. + uint32_t storage_index_; + + friend class ClassInitCheckEliminationTest; + friend class GlobalValueNumberingTest; + friend class LocalValueNumberingTest; +}; + +} // namespace art + +#endif // ART_COMPILER_DEX_MIR_FIELD_INFO_H_ diff --git a/compiler/dex/mir_graph.cc b/compiler/dex/mir_graph.cc index a12bf39e645..b9f54737f24 100644 --- a/compiler/dex/mir_graph.cc +++ b/compiler/dex/mir_graph.cc @@ -14,46 +14,26 @@ * limitations under the License. */ +#include "mir_graph.h" + +#include +#include + #include "base/stl_util.h" #include "compiler_internals.h" #include "dex_file-inl.h" +#include "dex_instruction-inl.h" +#include "dex/global_value_numbering.h" +#include "dex/quick/dex_file_to_method_inliner_map.h" +#include "dex/quick/dex_file_method_inliner.h" #include "leb128.h" -#include "mir_graph.h" +#include "pass_driver_me_post_opt.h" +#include "utils/scoped_arena_containers.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", @@ -69,11 +49,27 @@ const char* MIRGraph::extended_mir_op_names_[kMirOpLast - kMirOpFirst] = { "Check1", "Check2", "Select", + "ConstVector", + "MoveVector", + "PackedMultiply", + "PackedAddition", + "PackedSubtract", + "PackedShiftLeft", + "PackedSignedShiftRight", + "PackedUnsignedShiftRight", + "PackedAnd", + "PackedOr", + "PackedXor", + "PackedAddReduce", + "PackedReduce", + "PackedSet", + "ReserveVectorRegisters", + "ReturnVectorRegisters", }; MIRGraph::MIRGraph(CompilationUnit* cu, ArenaAllocator* arena) : reg_location_(NULL), - compiler_temps_(arena, 6, kGrowableArrayMisc), + block_id_map_(std::less(), arena->Adapter()), cu_(cu), ssa_base_vregs_(NULL), ssa_subscripts_(NULL), @@ -84,32 +80,55 @@ MIRGraph::MIRGraph(CompilationUnit* cu, ArenaAllocator* arena) use_counts_(arena, 256, kGrowableArrayMisc), raw_use_counts_(arena, 256, kGrowableArrayMisc), num_reachable_blocks_(0), + max_num_reachable_blocks_(0), dfs_order_(NULL), dfs_post_order_(NULL), dom_post_order_traversal_(NULL), + topological_order_(nullptr), + topological_order_loop_ends_(nullptr), + topological_order_indexes_(nullptr), + topological_order_loop_head_stack_(nullptr), i_dom_list_(NULL), def_block_matrix_(NULL), - temp_block_v_(NULL), - temp_dalvik_register_v_(NULL), - temp_ssa_register_v_(NULL), + temp_scoped_alloc_(), + temp_insn_data_(nullptr), + temp_bit_vector_size_(0u), + temp_bit_vector_(nullptr), + temp_gvn_(), 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), + dex_pc_to_block_map_(arena, 0, kGrowableArrayMisc), + m_units_(arena->Adapter()), + method_stack_(arena->Adapter()), current_method_(kInvalidEntry), current_offset_(kInvalidEntry), def_count_(0), opcode_count_(NULL), num_ssa_regs_(0), + extended_basic_blocks_(arena->Adapter()), method_sreg_(0), attributes_(METHOD_IS_LEAF), // Start with leaf assumption, change on encountering invoke. checkstats_(NULL), - special_case_(kNoHandler), - arena_(arena) { + arena_(arena), + backward_branches_(0), + forward_branches_(0), + compiler_temps_(arena, 6, kGrowableArrayMisc), + num_non_special_compiler_temps_(0), + max_available_non_special_compiler_temps_(0), + punt_to_interpreter_(false), + merged_df_flags_(0u), + ifield_lowering_infos_(arena, 0u), + sfield_lowering_infos_(arena, 0u), + method_lowering_infos_(arena, 0u), + gen_suspend_test_list_(arena, 0u), + qcm(nullptr) { try_block_addr_ = new (arena_) ArenaBitVector(arena_, 0, true /* expandable */); + max_available_special_compiler_temps_ = std::abs(static_cast(kVRegNonSpecialTempBaseReg)) + - std::abs(static_cast(kVRegTempBaseReg)); } MIRGraph::~MIRGraph() { @@ -119,26 +138,35 @@ MIRGraph::~MIRGraph() { /* * Parse an instruction, return the length of the instruction */ -int MIRGraph::ParseInsn(const uint16_t* code_ptr, DecodedInstruction* decoded_instruction) { - const Instruction* instruction = Instruction::At(code_ptr); - *decoded_instruction = DecodedInstruction(instruction); - - return instruction->SizeInCodeUnits(); +int MIRGraph::ParseInsn(const uint16_t* code_ptr, MIR::DecodedInstruction* decoded_instruction) { + const Instruction* inst = Instruction::At(code_ptr); + decoded_instruction->opcode = inst->Opcode(); + decoded_instruction->vA = inst->HasVRegA() ? inst->VRegA() : 0; + decoded_instruction->vB = inst->HasVRegB() ? inst->VRegB() : 0; + decoded_instruction->vB_wide = inst->HasWideVRegB() ? inst->WideVRegB() : 0; + decoded_instruction->vC = inst->HasVRegC() ? inst->VRegC() : 0; + if (inst->HasVarArgs()) { + inst->GetVarArgs(decoded_instruction->arg); + } + return inst->SizeInCodeUnits(); } /* Split an existing block from the specified code offset into two */ -BasicBlock* MIRGraph::SplitBlock(unsigned int code_offset, +BasicBlock* MIRGraph::SplitBlock(DexOffset code_offset, BasicBlock* orig_block, BasicBlock** immed_pred_block_p) { + DCHECK_GT(code_offset, orig_block->start_offset); MIR* insn = orig_block->first_mir_insn; + MIR* prev = NULL; while (insn) { if (insn->offset == code_offset) break; + prev = insn; insn = insn->next; } if (insn == NULL) { LOG(FATAL) << "Break split failed"; } - BasicBlock *bottom_block = NewMemBB(kDalvikByteCode, num_blocks_++); + BasicBlock* bottom_block = NewMemBB(kDalvikByteCode, num_blocks_++); block_list_.Insert(bottom_block); bottom_block->start_offset = code_offset; @@ -149,44 +177,46 @@ BasicBlock* MIRGraph::SplitBlock(unsigned int code_offset, 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); + if (bottom_block->taken != NullBasicBlockId) { + orig_block->taken = NullBasicBlockId; + BasicBlock* bb_taken = GetBasicBlock(bottom_block->taken); + bb_taken->predecessors->Delete(orig_block->id); + bb_taken->predecessors->Insert(bottom_block->id); } /* 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); + orig_block->fall_through = bottom_block->id; + bottom_block->predecessors->Insert(orig_block->id); + if (bottom_block->fall_through != NullBasicBlockId) { + BasicBlock* bb_fall_through = GetBasicBlock(bottom_block->fall_through); + bb_fall_through->predecessors->Delete(orig_block->id); + bb_fall_through->predecessors->Insert(bottom_block->id); } /* 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); + if (orig_block->successor_block_list_type != kNotUsed) { + bottom_block->successor_block_list_type = orig_block->successor_block_list_type; + bottom_block->successor_blocks = orig_block->successor_blocks; + orig_block->successor_block_list_type = kNotUsed; + orig_block->successor_blocks = nullptr; + GrowableArray::Iterator iterator(bottom_block->successor_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); + SuccessorBlockInfo* successor_block_info = iterator.Next(); + if (successor_block_info == nullptr) break; + BasicBlock* bb = GetBasicBlock(successor_block_info->block); + if (bb != nullptr) { + bb->predecessors->Delete(orig_block->id); + bb->predecessors->Insert(bottom_block->id); + } } } - orig_block->last_mir_insn = insn->prev; + orig_block->last_mir_insn = prev; + prev->next = nullptr; - insn->prev->next = NULL; - insn->prev = NULL; /* * Update the immediate predecessor block pointer so that outgoing edges * can be applied to the proper block. @@ -195,6 +225,34 @@ BasicBlock* MIRGraph::SplitBlock(unsigned int code_offset, DCHECK_EQ(*immed_pred_block_p, orig_block); *immed_pred_block_p = bottom_block; } + + // Associate dex instructions in the bottom block with the new container. + DCHECK(insn != nullptr); + DCHECK(insn != orig_block->first_mir_insn); + DCHECK(insn == bottom_block->first_mir_insn); + DCHECK_EQ(insn->offset, bottom_block->start_offset); + DCHECK(static_cast(insn->dalvikInsn.opcode) == kMirOpCheck || + !MIR::DecodedInstruction::IsPseudoMirOp(insn->dalvikInsn.opcode)); + DCHECK_EQ(dex_pc_to_block_map_.Get(insn->offset), orig_block->id); + MIR* p = insn; + dex_pc_to_block_map_.Put(p->offset, bottom_block->id); + while (p != bottom_block->last_mir_insn) { + p = p->next; + DCHECK(p != nullptr); + p->bb = bottom_block->id; + int opcode = p->dalvikInsn.opcode; + /* + * Some messiness here to ensure that we only enter real opcodes and only the + * first half of a potentially throwing instruction that has been split into + * CHECK and work portions. Since the 2nd half of a split operation is always + * the first in a BasicBlock, we can't hit it here. + */ + if ((opcode == kMirOpCheck) || !MIR::DecodedInstruction::IsPseudoMirOp(opcode)) { + DCHECK_EQ(dex_pc_to_block_map_.Get(p->offset), orig_block->id); + dex_pc_to_block_map_.Put(p->offset, bottom_block->id); + } + } + return bottom_block; } @@ -206,45 +264,43 @@ BasicBlock* MIRGraph::SplitBlock(unsigned int code_offset, * (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* MIRGraph::FindBlock(DexOffset code_offset, bool split, bool create, BasicBlock** immed_pred_block_p) { - BasicBlock* bb; - unsigned int i; - SafeMap::iterator it; + if (code_offset >= cu_->code_item->insns_size_in_code_units_) { + return NULL; + } - it = block_map_.find(code_offset); - if (it != block_map_.end()) { - return it->second; - } else if (!create) { + int block_id = dex_pc_to_block_map_.Get(code_offset); + BasicBlock* bb = (block_id == 0) ? NULL : block_list_.Get(block_id); + + if ((bb != NULL) && (bb->start_offset == code_offset)) { + // Does this containing block start with the desired instruction? + return bb; + } + + // No direct hit. + 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; - } - } + if (bb != NULL) { + // The target exists somewhere in an existing block. + return SplitBlock(code_offset, bb, bb == *immed_pred_block_p ? immed_pred_block_p : NULL); } - /* Create a new one */ + // Create a new block. bb = NewMemBB(kDalvikByteCode, num_blocks_++); block_list_.Insert(bb); bb->start_offset = code_offset; - block_map_.Put(bb->start_offset, bb); + dex_pc_to_block_map_.Put(bb->start_offset, bb->id); 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; + DexOffset offset; if (tries_size == 0) { return; @@ -253,14 +309,14 @@ void MIRGraph::ProcessTryCatchBlocks() { 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_; + DexOffset start_offset = pTry->start_addr_; + DexOffset 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 + // 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++) { @@ -274,11 +330,77 @@ void MIRGraph::ProcessTryCatchBlocks() { } } +bool MIRGraph::IsBadMonitorExitCatch(NarrowDexOffset monitor_exit_offset, + NarrowDexOffset catch_offset) { + // Catches for monitor-exit during stack unwinding have the pattern + // move-exception (move)* (goto)? monitor-exit throw + // In the currently generated dex bytecode we see these catching a bytecode range including + // either its own or an identical monitor-exit, http://b/15745363 . This function checks if + // it's the case for a given monitor-exit and catch block so that we can ignore it. + // (We don't want to ignore all monitor-exit catches since one could enclose a synchronized + // block in a try-block and catch the NPE, Error or Throwable and we should let it through; + // even though a throwing monitor-exit certainly indicates a bytecode error.) + const Instruction* monitor_exit = Instruction::At(cu_->code_item->insns_ + monitor_exit_offset); + DCHECK(monitor_exit->Opcode() == Instruction::MONITOR_EXIT); + int monitor_reg = monitor_exit->VRegA_11x(); + const Instruction* check_insn = Instruction::At(cu_->code_item->insns_ + catch_offset); + DCHECK(check_insn->Opcode() == Instruction::MOVE_EXCEPTION); + if (check_insn->VRegA_11x() == monitor_reg) { + // Unexpected move-exception to the same register. Probably not the pattern we're looking for. + return false; + } + check_insn = check_insn->Next(); + while (true) { + int dest = -1; + bool wide = false; + switch (check_insn->Opcode()) { + case Instruction::MOVE_WIDE: + wide = true; + // Intentional fall-through. + case Instruction::MOVE_OBJECT: + case Instruction::MOVE: + dest = check_insn->VRegA_12x(); + break; + + case Instruction::MOVE_WIDE_FROM16: + wide = true; + // Intentional fall-through. + case Instruction::MOVE_OBJECT_FROM16: + case Instruction::MOVE_FROM16: + dest = check_insn->VRegA_22x(); + break; + + case Instruction::MOVE_WIDE_16: + wide = true; + // Intentional fall-through. + case Instruction::MOVE_OBJECT_16: + case Instruction::MOVE_16: + dest = check_insn->VRegA_32x(); + break; + + case Instruction::GOTO: + case Instruction::GOTO_16: + case Instruction::GOTO_32: + check_insn = check_insn->RelativeAt(check_insn->GetTargetOffset()); + // Intentional fall-through. + default: + return check_insn->Opcode() == Instruction::MONITOR_EXIT && + check_insn->VRegA_11x() == monitor_reg; + } + + if (dest == monitor_reg || (wide && dest + 1 == monitor_reg)) { + return false; + } + + check_insn = check_insn->Next(); + } +} + /* 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, +BasicBlock* MIRGraph::ProcessCanBranch(BasicBlock* cur_block, MIR* insn, DexOffset cur_offset, + int width, int flags, const uint16_t* code_ptr, const uint16_t* code_end) { - int target = cur_offset; + DexOffset target = cur_offset; switch (insn->dalvikInsn.opcode) { case Instruction::GOTO: case Instruction::GOTO_16: @@ -306,14 +428,15 @@ BasicBlock* MIRGraph::ProcessCanBranch(BasicBlock* cur_block, MIR* insn, int cur default: LOG(FATAL) << "Unexpected opcode(" << insn->dalvikInsn.opcode << ") with kBranch set"; } - BasicBlock *taken_block = FindBlock(target, /* split */ true, /* create */ true, + CountBranch(target); + 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); + cur_block->taken = taken_block->id; + taken_block->predecessors->Insert(cur_block->id); /* Always terminate the current block for conditional branches */ if (flags & Instruction::kContinue) { - BasicBlock *fallthrough_block = FindBlock(cur_offset + width, + BasicBlock* fallthrough_block = FindBlock(cur_offset + width, /* * If the method is processed * in sequential order from the @@ -331,8 +454,8 @@ BasicBlock* MIRGraph::ProcessCanBranch(BasicBlock* cur_block, MIR* insn, int cur true, /* immed_pred_block_p */ &cur_block); - cur_block->fall_through = fallthrough_block; - fallthrough_block->predecessors->Insert(cur_block); + cur_block->fall_through = fallthrough_block->id; + fallthrough_block->predecessors->Insert(cur_block->id); } else if (code_ptr < code_end) { FindBlock(cur_offset + width, /* split */ false, /* create */ true, /* immed_pred_block_p */ NULL); @@ -341,8 +464,8 @@ BasicBlock* MIRGraph::ProcessCanBranch(BasicBlock* cur_block, MIR* insn, int cur } /* Process instructions with the kSwitch flag */ -void MIRGraph::ProcessCanSwitch(BasicBlock* cur_block, MIR* insn, int cur_offset, int width, - int flags) { +BasicBlock* MIRGraph::ProcessCanSwitch(BasicBlock* cur_block, MIR* insn, DexOffset cur_offset, + int width, int flags) { const uint16_t* switch_data = reinterpret_cast(GetCurrentInsns() + cur_offset + insn->dalvikInsn.vB); int size; @@ -366,7 +489,7 @@ void MIRGraph::ProcessCanSwitch(BasicBlock* cur_block, MIR* insn, int cur_offset 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 + keyTable = NULL; // Make the compiler happy. /* * Sparse switch data format: * ushort ident = 0x0200 magic value @@ -382,86 +505,97 @@ void MIRGraph::ProcessCanSwitch(BasicBlock* cur_block, MIR* insn, int cur_offset 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 + first_key = 0; // To make the compiler happy. } - if (cur_block->successor_block_list.block_list_type != kNotUsed) { + if (cur_block->successor_block_list_type != kNotUsed) { LOG(FATAL) << "Successor block list already in use: " - << static_cast(cur_block->successor_block_list.block_list_type); + << static_cast(cur_block->successor_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 = + cur_block->successor_block_list_type = + (insn->dalvikInsn.opcode == Instruction::PACKED_SWITCH) ? kPackedSwitch : kSparseSwitch; + cur_block->successor_blocks = new (arena_) GrowableArray(arena_, size, kGrowableArraySuccessorBlocks); for (i = 0; i < size; i++) { - BasicBlock *case_block = FindBlock(cur_offset + target_table[i], /* split */ true, + BasicBlock* case_block = FindBlock(cur_offset + target_table[i], /* split */ true, /* create */ true, /* immed_pred_block_p */ &cur_block); - SuccessorBlockInfo *successor_block_info = + SuccessorBlockInfo* successor_block_info = static_cast(arena_->Alloc(sizeof(SuccessorBlockInfo), - ArenaAllocator::kAllocSuccessor)); - successor_block_info->block = case_block; + kArenaAllocSuccessor)); + successor_block_info->block = case_block->id; 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); + cur_block->successor_blocks->Insert(successor_block_info); + case_block->predecessors->Insert(cur_block->id); } /* 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); + cur_block->fall_through = fallthrough_block->id; + fallthrough_block->predecessors->Insert(cur_block->id); + return 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, +BasicBlock* MIRGraph::ProcessCanThrow(BasicBlock* cur_block, MIR* insn, DexOffset 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); + bool is_throw = (insn->dalvikInsn.opcode == Instruction::THROW); + bool build_all_edges = + (cu_->disable_opt & (1 << kSuppressExceptionEdges)) || is_throw || in_try_block; /* In try block */ if (in_try_block) { CatchHandlerIterator iterator(*current_code_item_, cur_offset); - if (cur_block->successor_block_list.block_list_type != kNotUsed) { + if (cur_block->successor_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); + << static_cast(cur_block->successor_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*/, + BasicBlock* catch_block = FindBlock(iterator.GetHandlerAddress(), false /* split*/, false /* creat */, NULL /* immed_pred_block_p */); + if (insn->dalvikInsn.opcode == Instruction::MONITOR_EXIT && + IsBadMonitorExitCatch(insn->offset, catch_block->start_offset)) { + // Don't allow monitor-exit to catch its own exception, http://b/15745363 . + continue; + } + if (cur_block->successor_block_list_type == kNotUsed) { + cur_block->successor_block_list_type = kCatch; + cur_block->successor_blocks = new (arena_) GrowableArray( + arena_, 2, kGrowableArraySuccessorBlocks); + } catch_block->catch_entry = true; if (kIsDebugBuild) { catches_.insert(catch_block->start_offset); } - SuccessorBlockInfo *successor_block_info = reinterpret_cast - (arena_->Alloc(sizeof(SuccessorBlockInfo), ArenaAllocator::kAllocSuccessor)); - successor_block_info->block = catch_block; + SuccessorBlockInfo* successor_block_info = reinterpret_cast + (arena_->Alloc(sizeof(SuccessorBlockInfo), kArenaAllocSuccessor)); + successor_block_info->block = catch_block->id; successor_block_info->key = iterator.GetHandlerTypeIndex(); - cur_block->successor_block_list.blocks->Insert(successor_block_info); - catch_block->predecessors->Insert(cur_block); + cur_block->successor_blocks->Insert(successor_block_info); + catch_block->predecessors->Insert(cur_block->id); } - } else { - BasicBlock *eh_block = NewMemBB(kExceptionHandling, num_blocks_++); - cur_block->taken = eh_block; + in_try_block = (cur_block->successor_block_list_type != kNotUsed); + } + if (!in_try_block && build_all_edges) { + BasicBlock* eh_block = NewMemBB(kExceptionHandling, num_blocks_++); + cur_block->taken = eh_block->id; block_list_.Insert(eh_block); eh_block->start_offset = cur_offset; - eh_block->predecessors->Insert(cur_block); + eh_block->predecessors->Insert(cur_block->id); } - if (insn->dalvikInsn.opcode == Instruction::THROW) { + if (is_throw) { cur_block->explicit_throw = true; if (code_ptr < code_end) { - // Force creation of new block following THROW via side-effect + // Force creation of new block following THROW via side-effect. FindBlock(cur_offset + width, /* split */ false, /* create */ true, /* immed_pred_block_p */ NULL); } @@ -471,6 +605,16 @@ BasicBlock* MIRGraph::ProcessCanThrow(BasicBlock* cur_block, MIR* insn, int cur_ } } + if (!build_all_edges) { + /* + * Even though there is an exception edge here, control cannot return to this + * method. Thus, for the purposes of dataflow analysis and optimization, we can + * ignore the edge. Doing this reduces compile time, and increases the scope + * of the basic-block level optimization pass. + */ + return cur_block; + } + /* * Split the potentially-throwing instruction into two parts. * The first half will be a pseudo-op that captures the exception @@ -484,20 +628,21 @@ BasicBlock* MIRGraph::ProcessCanThrow(BasicBlock* cur_block, MIR* insn, int cur_ * pseudo exception edge MIR. Note also that this new block is * not automatically terminated after the work portion, and may * contain following instructions. + * + * Note also that the dex_pc_to_block_map_ entry for the potentially + * throwing instruction will refer to the original basic block. */ - BasicBlock *new_block = NewMemBB(kDalvikByteCode, num_blocks_++); + 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_->Alloc(sizeof(MIR), ArenaAllocator::kAllocMIR)); + cur_block->fall_through = new_block->id; + new_block->predecessors->Insert(cur_block->id); + MIR* new_insn = NewMIR(); *new_insn = *insn; - insn->dalvikInsn.opcode = - static_cast(kMirOpCheck); - // Associate the two halves + 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); + new_block->AppendMIR(new_insn); return new_block; } @@ -511,13 +656,17 @@ void MIRGraph::InlineMethod(const DexFile::CodeItem* code_item, uint32_t access_ 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)); + dex_file, current_code_item_, class_def_idx, method_idx, access_flags, + cu_->compiler_driver->GetVerifiedMethod(&dex_file, method_idx))); 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. + // TUNING: use better estimate of basic blocks for following resize. block_list_.Resize(block_list_.Size() + current_code_item_->insns_size_in_code_units_); + dex_pc_to_block_map_.SetSize(dex_pc_to_block_map_.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_); @@ -526,10 +675,15 @@ void MIRGraph::InlineMethod(const DexFile::CodeItem* code_item, uint32_t access_ if (current_method_ == 0) { DCHECK(entry_block_ == NULL); DCHECK(exit_block_ == NULL); - DCHECK_EQ(num_blocks_, 0); + DCHECK_EQ(num_blocks_, 0U); + // Use id 0 to represent a null block. + BasicBlock* null_block = NewMemBB(kNullBlock, num_blocks_++); + DCHECK_EQ(null_block->id, NullBasicBlockId); + null_block->hidden = true; + block_list_.Insert(null_block); entry_block_ = NewMemBB(kEntryBlock, num_blocks_++); - exit_block_ = NewMemBB(kExitBlock, num_blocks_++); block_list_.Insert(entry_block_); + exit_block_ = NewMemBB(kExitBlock, num_blocks_++); block_list_.Insert(exit_block_); // TODO: deprecate all "cu->" fields; move what's left to wherever CompilationUnit is allocated. cu_->dex_file = &dex_file; @@ -553,66 +707,45 @@ void MIRGraph::InlineMethod(const DexFile::CodeItem* code_item, uint32_t access_ } /* Current block to record parsed instructions */ - BasicBlock *cur_block = NewMemBB(kDalvikByteCode, num_blocks_++); - DCHECK_EQ(current_offset_, 0); + BasicBlock* cur_block = NewMemBB(kDalvikByteCode, num_blocks_++); + DCHECK_EQ(current_offset_, 0U); 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 */ + // TODO: for inlining support, insert at the insert point rather than entry block. + entry_block_->fall_through = cur_block->id; + cur_block->predecessors->Insert(entry_block_->id); + + /* 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_->Alloc(sizeof(bool) * num_patterns, ArenaAllocator::kAllocMisc)); - int pattern_pos = 0; + uint64_t merged_df_flags = 0u; /* Parse all instructions and put them into containing basic blocks */ while (code_ptr < code_end) { - MIR *insn = static_cast(arena_->Alloc(sizeof(MIR), ArenaAllocator::kAllocMIR)); + MIR *insn = NewMIR(); 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)]++; } - - /* 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++; - } - int flags = Instruction::FlagsOf(insn->dalvikInsn.opcode); + int verify_flags = Instruction::VerifyFlagsOf(insn->dalvikInsn.opcode); - int df_flags = oat_data_flow_attributes_[insn->dalvikInsn.opcode]; + uint64_t df_flags = GetDataFlowAttributes(insn); + merged_df_flags |= df_flags; if (df_flags & DF_HAS_DEFS) { def_count_ += (df_flags & DF_A_WIDE) ? 2 : 1; } - // Check for inline data block signatures + if (df_flags & DF_LVN) { + cur_block->use_lvn = true; // Run local value numbering on this basic block. + } + + // Check for inline data block signatures. if (opcode == Instruction::NOP) { // A simple NOP will have a width of 1 at this point, embedded data NOP > 1. if ((width == 1) && ((current_offset_ & 0x1) == 0x1) && ((code_end - code_ptr) > 1)) { @@ -626,17 +759,22 @@ void MIRGraph::InlineMethod(const DexFile::CodeItem* code_item, uint32_t access_ } if (width == 1) { // It is a simple nop - treat normally. - AppendMIR(cur_block, insn); + cur_block->AppendMIR(insn); } else { - DCHECK(cur_block->fall_through == NULL); - DCHECK(cur_block->taken == NULL); - // Unreachable instruction, mark for no continuation. + DCHECK(cur_block->fall_through == NullBasicBlockId); + DCHECK(cur_block->taken == NullBasicBlockId); + // Unreachable instruction, mark for no continuation and end basic block. flags &= ~Instruction::kContinue; + FindBlock(current_offset_ + width, /* split */ false, /* create */ true, + /* immed_pred_block_p */ NULL); } } else { - AppendMIR(cur_block, insn); + cur_block->AppendMIR(insn); } + // Associate the starting dex_pc for this opcode with its containing basic block. + dex_pc_to_block_map_.Put(insn->offset, cur_block->id); + code_ptr += width; if (flags & Instruction::kBranch) { @@ -644,8 +782,8 @@ void MIRGraph::InlineMethod(const DexFile::CodeItem* code_item, uint32_t access_ 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); + cur_block->fall_through = exit_block_->id; + exit_block_->predecessors->Insert(cur_block->id); /* * Terminate the current block if there are instructions * afterwards. @@ -662,10 +800,24 @@ void MIRGraph::InlineMethod(const DexFile::CodeItem* code_item, uint32_t access_ 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); + cur_block = ProcessCanSwitch(cur_block, insn, current_offset_, width, flags); + } + if (verify_flags & Instruction::kVerifyVarArgRange || + verify_flags & Instruction::kVerifyVarArgRangeNonZero) { + /* + * The Quick backend's runtime model includes a gap between a method's + * argument ("in") vregs and the rest of its vregs. Handling a range instruction + * which spans the gap is somewhat complicated, and should not happen + * in normal usage of dx. Punt to the interpreter. + */ + int first_reg_in_range = insn->dalvikInsn.vC; + int last_reg_in_range = first_reg_in_range + insn->dalvikInsn.vA - 1; + if (IsInVReg(first_reg_in_range) != IsInVReg(last_reg_in_range)) { + punt_to_interpreter_ = true; + } } current_offset_ += width; - BasicBlock *next_block = FindBlock(current_offset_, /* split */ false, /* create */ + BasicBlock* next_block = FindBlock(current_offset_, /* split */ false, /* create */ false, /* immed_pred_block_p */ NULL); if (next_block) { /* @@ -674,17 +826,19 @@ void MIRGraph::InlineMethod(const DexFile::CodeItem* code_item, uint32_t access_ * 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_); + DCHECK(cur_block->fall_through == NullBasicBlockId || + GetBasicBlock(cur_block->fall_through) == next_block || + GetBasicBlock(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); + if ((cur_block->fall_through == NullBasicBlockId) && (flags & Instruction::kContinue)) { + cur_block->fall_through = next_block->id; + next_block->predecessors->Insert(cur_block->id); } cur_block = next_block; } } + merged_df_flags_ = merged_df_flags; + if (cu_->enable_debug & (1 << kDebugDumpCFG)) { DumpCFG("/sdcard/1_post_parse_cfg/", true); } @@ -705,14 +859,37 @@ void MIRGraph::ShowOpcodeStats() { } } +uint64_t MIRGraph::GetDataFlowAttributes(Instruction::Code opcode) { + DCHECK_LT((size_t) opcode, (sizeof(oat_data_flow_attributes_) / sizeof(oat_data_flow_attributes_[0]))); + return oat_data_flow_attributes_[opcode]; +} + +uint64_t MIRGraph::GetDataFlowAttributes(MIR* mir) { + DCHECK(mir != nullptr); + Instruction::Code opcode = mir->dalvikInsn.opcode; + return GetDataFlowAttributes(opcode); +} + + +const char * MIRGraph::GetExtendedMirOpName(int index){ + return extended_mir_op_names_[index]; +} + // 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) { +void MIRGraph::DumpCFG(const char* dir_prefix, bool all_blocks, const char *suffix) { FILE* file; + static AtomicInteger cnt(0); + + // Increment counter to get a unique file number. + cnt++; + 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); + fname = StringPrintf("%s%s%x%s_%d.dot", dir_prefix, fname.c_str(), + GetBasicBlock(GetEntryBlock()->fall_through)->start_offset, + suffix == nullptr ? "" : suffix, + cnt.LoadRelaxed()); file = fopen(fname.c_str(), "w"); if (file == NULL) { return; @@ -726,9 +903,10 @@ void MIRGraph::DumpCFG(const char* dir_prefix, bool all_blocks) { 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; + BasicBlock* bb = GetBasicBlock(block_idx); + if (bb == NULL) continue; if (bb->block_type == kDead) continue; + if (bb->hidden) continue; if (bb->block_type == kEntryBlock) { fprintf(file, " entry_%d [shape=Mdiamond];\n", bb->id); } else if (bb->block_type == kExitBlock) { @@ -736,18 +914,41 @@ void MIRGraph::DumpCFG(const char* dir_prefix, bool all_blocks) { } else if (bb->block_type == kDalvikByteCode) { fprintf(file, " block%04x_%d [shape=record,label = \"{ \\\n", bb->start_offset, bb->id); - const MIR *mir; + 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 ? " | " : " "); + if (opcode > kMirOpSelect && opcode < kMirOpLast) { + if (opcode == kMirOpConstVector) { + fprintf(file, " {%04x %s %d %d %d %d %d %d\\l}%s\\\n", mir->offset, + MIRGraph::GetExtendedMirOpName(kMirOpConstVector - kMirOpFirst), + mir->dalvikInsn.vA, + mir->dalvikInsn.vB, + mir->dalvikInsn.arg[0], + mir->dalvikInsn.arg[1], + mir->dalvikInsn.arg[2], + mir->dalvikInsn.arg[3], + mir->next ? " | " : " "); + } else { + fprintf(file, " {%04x %s %d %d %d\\l}%s\\\n", mir->offset, + MIRGraph::GetExtendedMirOpName(opcode - kMirOpFirst), + mir->dalvikInsn.vA, + mir->dalvikInsn.vB, + mir->dalvikInsn.vC, + mir->next ? " | " : " "); + } + } else { + fprintf(file, " {%04x %s %s %s %s\\l}%s\\\n", mir->offset, + mir->ssa_rep ? GetDalvikDisassembly(mir) : + !MIR::DecodedInstruction::IsPseudoMirOp(opcode) ? + Instruction::Name(mir->dalvikInsn.opcode) : + MIRGraph::GetExtendedMirOpName(opcode - kMirOpFirst), + (mir->optimization_flags & MIR_IGNORE_RANGE_CHECK) != 0 ? " no_rangecheck" : " ", + (mir->optimization_flags & MIR_IGNORE_NULL_CHECK) != 0 ? " no_nullcheck" : " ", + (mir->optimization_flags & MIR_IGNORE_SUSPEND_CHECK) != 0 ? " no_suspendcheck" : " ", + mir->next ? " | " : " "); + } } fprintf(file, " }\"];\n\n"); } else if (bb->block_type == kExceptionHandling) { @@ -759,31 +960,30 @@ void MIRGraph::DumpCFG(const char* dir_prefix, bool all_blocks) { char block_name1[BLOCK_NAME_LEN], block_name2[BLOCK_NAME_LEN]; - if (bb->taken) { + if (bb->taken != NullBasicBlockId) { GetBlockName(bb, block_name1); - GetBlockName(bb->taken, block_name2); + GetBlockName(GetBasicBlock(bb->taken), block_name2); fprintf(file, " %s:s -> %s:n [style=dotted]\n", block_name1, block_name2); } - if (bb->fall_through) { + if (bb->fall_through != NullBasicBlockId) { GetBlockName(bb, block_name1); - GetBlockName(bb->fall_through, block_name2); + GetBlockName(GetBasicBlock(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) { + if (bb->successor_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(); + (bb->successor_block_list_type == kCatch) ? "Mrecord" : "record"); + GrowableArray::Iterator iterator(bb->successor_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; + BasicBlock* dest_block = GetBasicBlock(successor_block_info->block); SuccessorBlockInfo *next_successor_block_info = iterator.Next(); fprintf(file, " { %04x: %04x\\l}%s\\\n", @@ -800,21 +1000,19 @@ void MIRGraph::DumpCFG(const char* dir_prefix, bool all_blocks) { 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); + // Link the successor pseudo-block with all of its potential targets. + GrowableArray::Iterator iter(bb->successor_blocks); - succ_id = 0; - while (true) { - SuccessorBlockInfo *successor_block_info = iter.Next(); - if (successor_block_info == NULL) break; + succ_id = 0; + while (true) { + SuccessorBlockInfo* successor_block_info = iter.Next(); + if (successor_block_info == NULL) break; - BasicBlock *dest_block = successor_block_info->block; + BasicBlock* dest_block = GetBasicBlock(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); - } + 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"); @@ -825,7 +1023,7 @@ void MIRGraph::DumpCFG(const char* dir_prefix, bool all_blocks) { fprintf(file, " cfg%s [label=\"%s\", shape=none];\n", block_name1, block_name1); if (bb->i_dom) { - GetBlockName(bb->i_dom, block_name2); + GetBlockName(GetBasicBlock(bb->i_dom), block_name2); fprintf(file, " cfg%s:s -> cfg%s:n\n\n", block_name2, block_name1); } } @@ -834,66 +1032,209 @@ void MIRGraph::DumpCFG(const char* dir_prefix, bool all_blocks) { 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; +/* Insert an MIR instruction to the end of a basic block. */ +void BasicBlock::AppendMIR(MIR* mir) { + // Insert it after the last MIR. + InsertMIRListAfter(last_mir_insn, mir, mir); +} + +void BasicBlock::AppendMIRList(MIR* first_list_mir, MIR* last_list_mir) { + // Insert it after the last MIR. + InsertMIRListAfter(last_mir_insn, first_list_mir, last_list_mir); +} + +void BasicBlock::AppendMIRList(const std::vector& insns) { + for (std::vector::const_iterator it = insns.begin(); it != insns.end(); it++) { + MIR* new_mir = *it; + + // Add a copy of each MIR. + InsertMIRListAfter(last_mir_insn, new_mir, new_mir); + } +} + +/* Insert a MIR instruction after the specified MIR. */ +void BasicBlock::InsertMIRAfter(MIR* current_mir, MIR* new_mir) { + InsertMIRListAfter(current_mir, new_mir, new_mir); +} + +void BasicBlock::InsertMIRListAfter(MIR* insert_after, MIR* first_list_mir, MIR* last_list_mir) { + // If no MIR, we are done. + if (first_list_mir == nullptr || last_list_mir == nullptr) { + return; + } + + // If insert_after is null, assume BB is empty. + if (insert_after == nullptr) { + first_mir_insn = first_list_mir; + last_mir_insn = last_list_mir; + last_list_mir->next = nullptr; } else { - bb->last_mir_insn->next = mir; - mir->prev = bb->last_mir_insn; - mir->next = NULL; - bb->last_mir_insn = mir; + MIR* after_list = insert_after->next; + insert_after->next = first_list_mir; + last_list_mir->next = after_list; + if (after_list == nullptr) { + last_mir_insn = last_list_mir; + } + } + + // Set this BB to be the basic block of the MIRs. + MIR* last = last_list_mir->next; + for (MIR* mir = first_list_mir; mir != last; mir = mir->next) { + mir->bb = id; + } +} + +/* Insert an MIR instruction to the head of a basic block. */ +void BasicBlock::PrependMIR(MIR* mir) { + InsertMIRListBefore(first_mir_insn, mir, mir); +} + +void BasicBlock::PrependMIRList(MIR* first_list_mir, MIR* last_list_mir) { + // Insert it before the first MIR. + InsertMIRListBefore(first_mir_insn, first_list_mir, last_list_mir); +} + +void BasicBlock::PrependMIRList(const std::vector& to_add) { + for (std::vector::const_iterator it = to_add.begin(); it != to_add.end(); it++) { + MIR* mir = *it; + + InsertMIRListBefore(first_mir_insn, mir, 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; +/* Insert a MIR instruction before the specified MIR. */ +void BasicBlock::InsertMIRBefore(MIR* current_mir, MIR* new_mir) { + // Insert as a single element list. + return InsertMIRListBefore(current_mir, new_mir, new_mir); +} + +MIR* BasicBlock::FindPreviousMIR(MIR* mir) { + MIR* current = first_mir_insn; + + while (current != nullptr) { + MIR* next = current->next; + + if (next == mir) { + return current; + } + + current = next; + } + + return nullptr; +} + +void BasicBlock::InsertMIRListBefore(MIR* insert_before, MIR* first_list_mir, MIR* last_list_mir) { + // If no MIR, we are done. + if (first_list_mir == nullptr || last_list_mir == nullptr) { + return; + } + + // If insert_before is null, assume BB is empty. + if (insert_before == nullptr) { + first_mir_insn = first_list_mir; + last_mir_insn = last_list_mir; + last_list_mir->next = nullptr; } else { - bb->first_mir_insn->prev = mir; - mir->next = bb->first_mir_insn; - mir->prev = NULL; - bb->first_mir_insn = mir; + if (first_mir_insn == insert_before) { + last_list_mir->next = first_mir_insn; + first_mir_insn = first_list_mir; + } else { + // Find the preceding MIR. + MIR* before_list = FindPreviousMIR(insert_before); + DCHECK(before_list != nullptr); + before_list->next = first_list_mir; + last_list_mir->next = insert_before; + } + } + + // Set this BB to be the basic block of the MIRs. + for (MIR* mir = first_list_mir; mir != last_list_mir->next; mir = mir->next) { + mir->bb = id; } } -/* 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; +bool BasicBlock::RemoveMIR(MIR* mir) { + // Remove as a single element list. + return RemoveMIRList(mir, mir); +} + +bool BasicBlock::RemoveMIRList(MIR* first_list_mir, MIR* last_list_mir) { + if (first_list_mir == nullptr) { + return false; + } + + // Try to find the MIR. + MIR* before_list = nullptr; + MIR* after_list = nullptr; + + // If we are removing from the beginning of the MIR list. + if (first_mir_insn == first_list_mir) { + before_list = nullptr; + } else { + before_list = FindPreviousMIR(first_list_mir); + if (before_list == nullptr) { + // We did not find the mir. + return false; + } + } + + // Remove the BB information and also find the after_list. + for (MIR* mir = first_list_mir; mir != last_list_mir; mir = mir->next) { + mir->bb = NullBasicBlockId; + } + + after_list = last_list_mir->next; - if (new_mir->next) { - /* Is not the last MIR in the block */ - new_mir->next->prev = new_mir; + // If there is nothing before the list, after_list is the first_mir. + if (before_list == nullptr) { + first_mir_insn = after_list; } else { - /* Is the last MIR in the block */ - bb->last_mir_insn = new_mir; + before_list->next = after_list; + } + + // If there is nothing after the list, before_list is last_mir. + if (after_list == nullptr) { + last_mir_insn = before_list; + } + + return true; +} + +MIR* BasicBlock::GetNextUnconditionalMir(MIRGraph* mir_graph, MIR* current) { + MIR* next_mir = nullptr; + + if (current != nullptr) { + next_mir = current->next; + } + + if (next_mir == nullptr) { + // Only look for next MIR that follows unconditionally. + if ((taken == NullBasicBlockId) && (fall_through != NullBasicBlockId)) { + next_mir = mir_graph->GetBasicBlock(fall_through)->first_mir_insn; + } } + + return next_mir; } char* MIRGraph::GetDalvikDisassembly(const MIR* mir) { - DecodedInstruction insn = mir->dalvikInsn; + 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 + 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(MIRGraph::GetExtendedMirOpName(opcode - kMirOpFirst)); str.append(": "); - // Recover the original Dex instruction + // Recover the original Dex instruction. insn = mir->meta.throw_insn->dalvikInsn; ssa_rep = mir->meta.throw_insn->ssa_rep; defs = ssa_rep->num_defs; @@ -901,13 +1242,14 @@ char* MIRGraph::GetDalvikDisassembly(const MIR* mir) { opcode = insn.opcode; } else if (opcode == kMirOpNop) { str.append("["); - insn.opcode = mir->meta.original_opcode; - opcode = mir->meta.original_opcode; + // Recover original opcode. + insn.opcode = Instruction::At(current_code_item_->insns_ + mir->offset)->Opcode(); + opcode = insn.opcode; nop = true; } - if (opcode >= kMirOpFirst) { - str.append(extended_mir_op_names_[opcode - kMirOpFirst]); + if (MIR::DecodedInstruction::IsPseudoMirOp(opcode)) { + str.append(MIRGraph::GetExtendedMirOpName(opcode - kMirOpFirst)); } else { dalvik_format = Instruction::FormatOf(insn.opcode); flags = Instruction::FlagsOf(insn.opcode); @@ -915,7 +1257,7 @@ char* MIRGraph::GetDalvikDisassembly(const MIR* mir) { } if (opcode == kMirOpPhi) { - int* incoming = reinterpret_cast(insn.vB); + BasicBlockId* incoming = mir->meta.phi_incoming; str.append(StringPrintf(" %s = (%s", GetSSANameWithConst(ssa_rep->defs[0], true).c_str(), GetSSANameWithConst(ssa_rep->uses[0], true).c_str())); @@ -951,7 +1293,7 @@ char* MIRGraph::GetDalvikDisassembly(const MIR* mir) { 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 + // 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) { @@ -972,28 +1314,28 @@ char* MIRGraph::GetDalvikDisassembly(const MIR* mir) { } } switch (dalvik_format) { - case Instruction::k11n: // Add one immediate from vB + 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)); + case Instruction::k51l: // Add one wide immediate. + str.append(StringPrintf(", #%" PRId64, insn.vB_wide)); break; - case Instruction::k21c: // One register, one string/type/method index + 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 + 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::k22s: // Add one immediate from vC. case Instruction::k22b: str.append(StringPrintf(", #%d", insn.vC)); break; default: { - // Nothing left to print + // Nothing left to print. } } } @@ -1001,7 +1343,7 @@ char* MIRGraph::GetDalvikDisassembly(const MIR* mir) { str.append("]--optimized away"); } int length = str.length() + 1; - ret = static_cast(arena_->Alloc(length, ArenaAllocator::kAllocDFInfo)); + ret = static_cast(arena_->Alloc(length, kArenaAllocDFInfo)); strncpy(ret, str.c_str(), length); return ret; } @@ -1027,12 +1369,12 @@ std::string MIRGraph::GetSSAName(int 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 + // 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), + return StringPrintf("v%d_%d#0x%" PRIx64, 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), @@ -1065,7 +1407,7 @@ void MIRGraph::GetBlockName(BasicBlock* bb, char* name) { } const char* MIRGraph::GetShortyFromTargetIdx(int target_idx) { - // FIXME: use current code unit for inline support. + // TODO: for inlining support, use current code unit. const DexFile::MethodId& method_id = cu_->dex_file->GetMethodId(target_idx); return cu_->dex_file->GetShorty(method_id.proto_idx_); } @@ -1074,6 +1416,7 @@ const char* MIRGraph::GetShortyFromTargetIdx(int target_idx) { void MIRGraph::DumpMIRGraph() { BasicBlock* bb; const char* block_type_names[] = { + "Null Block", "Entry Block", "Code Block", "Exit Block", @@ -1095,13 +1438,13 @@ void MIRGraph::DumpMIRGraph() { 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->taken != NullBasicBlockId) { + LOG(INFO) << " Taken branch: block " << bb->taken + << "(0x" << std::hex << GetBasicBlock(bb->taken)->start_offset << ")"; } - if (bb->fall_through) { - LOG(INFO) << " Fallthrough : block " << bb->fall_through->id - << " (0x" << std::hex << bb->fall_through->start_offset << ")"; + if (bb->fall_through != NullBasicBlockId) { + LOG(INFO) << " Fallthrough : block " << bb->fall_through + << " (0x" << std::hex << GetBasicBlock(bb->fall_through)->start_offset << ")"; } } } @@ -1115,18 +1458,17 @@ void MIRGraph::DumpMIRGraph() { CallInfo* MIRGraph::NewMemCallInfo(BasicBlock* bb, MIR* mir, InvokeType type, bool is_range) { CallInfo* info = static_cast(arena_->Alloc(sizeof(CallInfo), - ArenaAllocator::kAllocMisc)); + kArenaAllocMisc)); 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_->Alloc(sizeof(RegLocation) * info->num_arg_words, ArenaAllocator::kAllocMisc)); + (arena_->Alloc(sizeof(RegLocation) * info->num_arg_words, kArenaAllocMisc)); for (int i = 0; i < info->num_arg_words; i++) { info->args[i] = GetRawSrc(mir, i); } @@ -1135,22 +1477,715 @@ CallInfo* MIRGraph::NewMemCallInfo(BasicBlock* bb, MIR* mir, InvokeType type, info->is_range = is_range; info->index = mir->dalvikInsn.vB; info->offset = mir->offset; + info->mir = mir; return info; } +// Allocate a new MIR. +MIR* MIRGraph::NewMIR() { + MIR* mir = new (arena_) MIR(); + return mir; +} + // Allocate a new basic block. BasicBlock* MIRGraph::NewMemBB(BBType block_type, int block_id) { - BasicBlock* bb = static_cast(arena_->Alloc(sizeof(BasicBlock), - ArenaAllocator::kAllocBB)); + BasicBlock* bb = new (arena_) BasicBlock(); + bb->block_type = block_type; bb->id = block_id; // TUNING: better estimate of the exit block predecessors? - bb->predecessors = new (arena_) GrowableArray(arena_, + bb->predecessors = new (arena_) GrowableArray(arena_, (block_type == kExitBlock) ? 2048 : 2, kGrowableArrayPredecessors); - bb->successor_block_list.block_list_type = kNotUsed; + bb->successor_block_list_type = kNotUsed; block_id_map_.Put(block_id, block_id); return bb; } +void MIRGraph::InitializeConstantPropagation() { + is_constant_v_ = new (arena_) ArenaBitVector(arena_, GetNumSSARegs(), false); + constant_values_ = static_cast(arena_->Alloc(sizeof(int) * GetNumSSARegs(), kArenaAllocDFInfo)); +} + +void MIRGraph::InitializeMethodUses() { + // The gate starts by initializing the use counts. + int num_ssa_regs = GetNumSSARegs(); + use_counts_.Resize(num_ssa_regs + 32); + raw_use_counts_.Resize(num_ssa_regs + 32); + // reset both lists to restart fresh + use_counts_.Reset(); + raw_use_counts_.Reset(); + // Initialize list. + for (int i = 0; i < num_ssa_regs; i++) { + use_counts_.Insert(0); + raw_use_counts_.Insert(0); + } +} + +void MIRGraph::SSATransformationStart() { + DCHECK(temp_scoped_alloc_.get() == nullptr); + temp_scoped_alloc_.reset(ScopedArenaAllocator::Create(&cu_->arena_stack)); + temp_bit_vector_size_ = cu_->num_dalvik_registers; + temp_bit_vector_ = new (temp_scoped_alloc_.get()) ArenaBitVector( + temp_scoped_alloc_.get(), temp_bit_vector_size_, false, kBitMapRegisterV); + + // Update the maximum number of reachable blocks. + max_num_reachable_blocks_ = num_reachable_blocks_; +} + +void MIRGraph::SSATransformationEnd() { + // Verify the dataflow information after the pass. + if (cu_->enable_debug & (1 << kDebugVerifyDataflow)) { + VerifyDataflow(); + } + + temp_bit_vector_size_ = 0u; + temp_bit_vector_ = nullptr; + DCHECK(temp_scoped_alloc_.get() != nullptr); + temp_scoped_alloc_.reset(); +} + +static BasicBlock* SelectTopologicalSortOrderFallBack( + MIRGraph* mir_graph, const ArenaBitVector* current_loop, + const ScopedArenaVector* visited_cnt_values, ScopedArenaAllocator* allocator, + ScopedArenaVector* tmp_stack) { + // No true loop head has been found but there may be true loop heads after the mess we need + // to resolve. To avoid taking one of those, pick the candidate with the highest number of + // reachable unvisited nodes. That candidate will surely be a part of a loop. + BasicBlock* fall_back = nullptr; + size_t fall_back_num_reachable = 0u; + // Reuse the same bit vector for each candidate to mark reachable unvisited blocks. + ArenaBitVector candidate_reachable(allocator, mir_graph->GetNumBlocks(), false, kBitMapMisc); + AllNodesIterator iter(mir_graph); + for (BasicBlock* candidate = iter.Next(); candidate != nullptr; candidate = iter.Next()) { + if (candidate->hidden || // Hidden, or + candidate->visited || // already processed, or + (*visited_cnt_values)[candidate->id] == 0u || // no processed predecessors, or + (current_loop != nullptr && // outside current loop. + !current_loop->IsBitSet(candidate->id))) { + continue; + } + DCHECK(tmp_stack->empty()); + tmp_stack->push_back(candidate->id); + candidate_reachable.ClearAllBits(); + size_t num_reachable = 0u; + while (!tmp_stack->empty()) { + BasicBlockId current_id = tmp_stack->back(); + tmp_stack->pop_back(); + BasicBlock* current_bb = mir_graph->GetBasicBlock(current_id); + DCHECK(current_bb != nullptr); + ChildBlockIterator child_iter(current_bb, mir_graph); + BasicBlock* child_bb = child_iter.Next(); + for ( ; child_bb != nullptr; child_bb = child_iter.Next()) { + DCHECK(!child_bb->hidden); + if (child_bb->visited || // Already processed, or + (current_loop != nullptr && // outside current loop. + !current_loop->IsBitSet(child_bb->id))) { + continue; + } + if (!candidate_reachable.IsBitSet(child_bb->id)) { + candidate_reachable.SetBit(child_bb->id); + tmp_stack->push_back(child_bb->id); + num_reachable += 1u; + } + } + } + if (fall_back_num_reachable < num_reachable) { + fall_back_num_reachable = num_reachable; + fall_back = candidate; + } + } + return fall_back; +} + +// Compute from which unvisited blocks is bb_id reachable through unvisited blocks. +static void ComputeUnvisitedReachableFrom(MIRGraph* mir_graph, BasicBlockId bb_id, + ArenaBitVector* reachable, + ScopedArenaVector* tmp_stack) { + // NOTE: Loop heads indicated by the "visited" flag. + DCHECK(tmp_stack->empty()); + reachable->ClearAllBits(); + tmp_stack->push_back(bb_id); + while (!tmp_stack->empty()) { + BasicBlockId current_id = tmp_stack->back(); + tmp_stack->pop_back(); + BasicBlock* current_bb = mir_graph->GetBasicBlock(current_id); + DCHECK(current_bb != nullptr); + GrowableArray::Iterator iter(current_bb->predecessors); + BasicBlock* pred_bb = mir_graph->GetBasicBlock(iter.Next()); + for ( ; pred_bb != nullptr; pred_bb = mir_graph->GetBasicBlock(iter.Next())) { + if (!pred_bb->visited && !reachable->IsBitSet(pred_bb->id)) { + reachable->SetBit(pred_bb->id); + tmp_stack->push_back(pred_bb->id); + } + } + } +} + +void MIRGraph::ComputeTopologicalSortOrder() { + ScopedArenaAllocator allocator(&cu_->arena_stack); + unsigned int num_blocks = GetNumBlocks(); + + ScopedArenaQueue q(allocator.Adapter()); + ScopedArenaVector visited_cnt_values(num_blocks, 0u, allocator.Adapter()); + ScopedArenaVector loop_head_stack(allocator.Adapter()); + size_t max_nested_loops = 0u; + ArenaBitVector loop_exit_blocks(&allocator, num_blocks, false, kBitMapMisc); + loop_exit_blocks.ClearAllBits(); + + // Count the number of blocks to process and add the entry block(s). + GrowableArray::Iterator iterator(&block_list_); + unsigned int num_blocks_to_process = 0u; + for (BasicBlock* bb = iterator.Next(); bb != nullptr; bb = iterator.Next()) { + if (bb->hidden == true) { + continue; + } + + num_blocks_to_process += 1u; + + if (bb->predecessors->Size() == 0u) { + // Add entry block to the queue. + q.push(bb); + } + } + + // Create the topological order if need be. + if (topological_order_ == nullptr) { + topological_order_ = new (arena_) GrowableArray(arena_, num_blocks); + topological_order_loop_ends_ = new (arena_) GrowableArray(arena_, num_blocks); + topological_order_indexes_ = new (arena_) GrowableArray(arena_, num_blocks); + } + topological_order_->Reset(); + topological_order_loop_ends_->Reset(); + topological_order_indexes_->Reset(); + topological_order_loop_ends_->Resize(num_blocks); + topological_order_indexes_->Resize(num_blocks); + for (BasicBlockId i = 0; i != num_blocks; ++i) { + topological_order_loop_ends_->Insert(0u); + topological_order_indexes_->Insert(static_cast(-1)); + } + + // Mark all blocks as unvisited. + ClearAllVisitedFlags(); + + // For loop heads, keep track from which blocks they are reachable not going through other + // loop heads. Other loop heads are excluded to detect the heads of nested loops. The children + // in this set go into the loop body, the other children are jumping over the loop. + ScopedArenaVector loop_head_reachable_from(allocator.Adapter()); + loop_head_reachable_from.resize(num_blocks, nullptr); + // Reuse the same temp stack whenever calculating a loop_head_reachable_from[loop_head_id]. + ScopedArenaVector tmp_stack(allocator.Adapter()); + + while (num_blocks_to_process != 0u) { + BasicBlock* bb = nullptr; + if (!q.empty()) { + num_blocks_to_process -= 1u; + // Get top. + bb = q.front(); + q.pop(); + if (bb->visited) { + // Loop head: it was already processed, mark end and copy exit blocks to the queue. + DCHECK(q.empty()) << PrettyMethod(cu_->method_idx, *cu_->dex_file); + uint16_t idx = static_cast(topological_order_->Size()); + topological_order_loop_ends_->Put(topological_order_indexes_->Get(bb->id), idx); + DCHECK_EQ(loop_head_stack.back(), bb->id); + loop_head_stack.pop_back(); + ArenaBitVector* reachable = + loop_head_stack.empty() ? nullptr : loop_head_reachable_from[loop_head_stack.back()]; + for (BasicBlockId candidate_id : loop_exit_blocks.Indexes()) { + if (reachable == nullptr || reachable->IsBitSet(candidate_id)) { + q.push(GetBasicBlock(candidate_id)); + // NOTE: The BitVectorSet::IndexIterator will not check the pointed-to bit again, + // so clearing the bit has no effect on the iterator. + loop_exit_blocks.ClearBit(candidate_id); + } + } + continue; + } + } else { + // Find the new loop head. + AllNodesIterator iter(this); + while (true) { + BasicBlock* candidate = iter.Next(); + if (candidate == nullptr) { + // We did not find a true loop head, fall back to a reachable block in any loop. + ArenaBitVector* current_loop = + loop_head_stack.empty() ? nullptr : loop_head_reachable_from[loop_head_stack.back()]; + bb = SelectTopologicalSortOrderFallBack(this, current_loop, &visited_cnt_values, + &allocator, &tmp_stack); + DCHECK(bb != nullptr) << PrettyMethod(cu_->method_idx, *cu_->dex_file); + if (kIsDebugBuild && cu_->dex_file != nullptr) { + LOG(INFO) << "Topological sort order: Using fall-back in " + << PrettyMethod(cu_->method_idx, *cu_->dex_file) << " BB #" << bb->id + << " @0x" << std::hex << bb->start_offset + << ", num_blocks = " << std::dec << num_blocks; + } + break; + } + if (candidate->hidden || // Hidden, or + candidate->visited || // already processed, or + visited_cnt_values[candidate->id] == 0u || // no processed predecessors, or + (!loop_head_stack.empty() && // outside current loop. + !loop_head_reachable_from[loop_head_stack.back()]->IsBitSet(candidate->id))) { + continue; + } + + GrowableArray::Iterator pred_iter(candidate->predecessors); + BasicBlock* pred_bb = GetBasicBlock(pred_iter.Next()); + for ( ; pred_bb != nullptr; pred_bb = GetBasicBlock(pred_iter.Next())) { + if (pred_bb != candidate && !pred_bb->visited && + !pred_bb->dominators->IsBitSet(candidate->id)) { + break; // Keep non-null pred_bb to indicate failure. + } + } + if (pred_bb == nullptr) { + bb = candidate; + break; + } + } + // Compute blocks from which the loop head is reachable and process those blocks first. + ArenaBitVector* reachable = + new (&allocator) ArenaBitVector(&allocator, num_blocks, false, kBitMapMisc); + loop_head_reachable_from[bb->id] = reachable; + ComputeUnvisitedReachableFrom(this, bb->id, reachable, &tmp_stack); + // Now mark as loop head. (Even if it's only a fall back when we don't find a true loop.) + loop_head_stack.push_back(bb->id); + max_nested_loops = std::max(max_nested_loops, loop_head_stack.size()); + } + + DCHECK_EQ(bb->hidden, false); + DCHECK_EQ(bb->visited, false); + bb->visited = true; + + // Now add the basic block. + uint16_t idx = static_cast(topological_order_->Size()); + topological_order_indexes_->Put(bb->id, idx); + topological_order_->Insert(bb->id); + + // Update visited_cnt_values for children. + ChildBlockIterator succIter(bb, this); + BasicBlock* successor = succIter.Next(); + for ( ; successor != nullptr; successor = succIter.Next()) { + if (successor->hidden) { + continue; + } + + // One more predecessor was visited. + visited_cnt_values[successor->id] += 1u; + if (visited_cnt_values[successor->id] == successor->predecessors->Size()) { + if (loop_head_stack.empty() || + loop_head_reachable_from[loop_head_stack.back()]->IsBitSet(successor->id)) { + q.push(successor); + } else { + DCHECK(!loop_exit_blocks.IsBitSet(successor->id)); + loop_exit_blocks.SetBit(successor->id); + } + } + } + } + + // Prepare the loop head stack for iteration. + topological_order_loop_head_stack_ = + new (arena_) GrowableArray>(arena_, max_nested_loops); +} + +bool BasicBlock::IsExceptionBlock() const { + if (block_type == kExceptionHandling) { + return true; + } + return false; +} + +bool MIRGraph::HasSuspendTestBetween(BasicBlock* source, BasicBlockId target_id) { + BasicBlock* target = GetBasicBlock(target_id); + + if (source == nullptr || target == nullptr) + return false; + + int idx; + for (idx = gen_suspend_test_list_.Size() - 1; idx >= 0; idx--) { + BasicBlock* bb = gen_suspend_test_list_.Get(idx); + if (bb == source) + return true; // The block has been inserted by a suspend check before. + if (source->dominators->IsBitSet(bb->id) && bb->dominators->IsBitSet(target_id)) + return true; + } + + return false; +} + +ChildBlockIterator::ChildBlockIterator(BasicBlock* bb, MIRGraph* mir_graph) + : basic_block_(bb), mir_graph_(mir_graph), visited_fallthrough_(false), + visited_taken_(false), have_successors_(false) { + // Check if we actually do have successors. + if (basic_block_ != 0 && basic_block_->successor_block_list_type != kNotUsed) { + have_successors_ = true; + successor_iter_.Reset(basic_block_->successor_blocks); + } +} + +BasicBlock* ChildBlockIterator::Next() { + // We check if we have a basic block. If we don't we cannot get next child. + if (basic_block_ == nullptr) { + return nullptr; + } + + // If we haven't visited fallthrough, return that. + if (visited_fallthrough_ == false) { + visited_fallthrough_ = true; + + BasicBlock* result = mir_graph_->GetBasicBlock(basic_block_->fall_through); + if (result != nullptr) { + return result; + } + } + + // If we haven't visited taken, return that. + if (visited_taken_ == false) { + visited_taken_ = true; + + BasicBlock* result = mir_graph_->GetBasicBlock(basic_block_->taken); + if (result != nullptr) { + return result; + } + } + + // We visited both taken and fallthrough. Now check if we have successors we need to visit. + if (have_successors_ == true) { + // Get information about next successor block. + for (SuccessorBlockInfo* successor_block_info = successor_iter_.Next(); + successor_block_info != nullptr; + successor_block_info = successor_iter_.Next()) { + // If block was replaced by zero block, take next one. + if (successor_block_info->block != NullBasicBlockId) { + return mir_graph_->GetBasicBlock(successor_block_info->block); + } + } + } + + // We do not have anything. + return nullptr; +} + +BasicBlock* BasicBlock::Copy(CompilationUnit* c_unit) { + MIRGraph* mir_graph = c_unit->mir_graph.get(); + return Copy(mir_graph); +} + +BasicBlock* BasicBlock::Copy(MIRGraph* mir_graph) { + BasicBlock* result_bb = mir_graph->CreateNewBB(block_type); + + // We don't do a memcpy style copy here because it would lead to a lot of things + // to clean up. Let us do it by hand instead. + // Copy in taken and fallthrough. + result_bb->fall_through = fall_through; + result_bb->taken = taken; + + // Copy successor links if needed. + ArenaAllocator* arena = mir_graph->GetArena(); + + result_bb->successor_block_list_type = successor_block_list_type; + if (result_bb->successor_block_list_type != kNotUsed) { + size_t size = successor_blocks->Size(); + result_bb->successor_blocks = new (arena) GrowableArray(arena, size, kGrowableArraySuccessorBlocks); + GrowableArray::Iterator iterator(successor_blocks); + while (true) { + SuccessorBlockInfo* sbi_old = iterator.Next(); + if (sbi_old == nullptr) { + break; + } + SuccessorBlockInfo* sbi_new = static_cast(arena->Alloc(sizeof(SuccessorBlockInfo), kArenaAllocSuccessor)); + memcpy(sbi_new, sbi_old, sizeof(SuccessorBlockInfo)); + result_bb->successor_blocks->Insert(sbi_new); + } + } + + // Copy offset, method. + result_bb->start_offset = start_offset; + + // Now copy instructions. + for (MIR* mir = first_mir_insn; mir != 0; mir = mir->next) { + // Get a copy first. + MIR* copy = mir->Copy(mir_graph); + + // Append it. + result_bb->AppendMIR(copy); + } + + return result_bb; +} + +MIR* MIR::Copy(MIRGraph* mir_graph) { + MIR* res = mir_graph->NewMIR(); + *res = *this; + + // Remove links + res->next = nullptr; + res->bb = NullBasicBlockId; + res->ssa_rep = nullptr; + + return res; +} + +MIR* MIR::Copy(CompilationUnit* c_unit) { + return Copy(c_unit->mir_graph.get()); +} + +uint32_t SSARepresentation::GetStartUseIndex(Instruction::Code opcode) { + // Default result. + int res = 0; + + // We are basically setting the iputs to their igets counterparts. + switch (opcode) { + case Instruction::IPUT: + case Instruction::IPUT_OBJECT: + case Instruction::IPUT_BOOLEAN: + case Instruction::IPUT_BYTE: + case Instruction::IPUT_CHAR: + case Instruction::IPUT_SHORT: + case Instruction::IPUT_QUICK: + case Instruction::IPUT_OBJECT_QUICK: + case Instruction::APUT: + case Instruction::APUT_OBJECT: + case Instruction::APUT_BOOLEAN: + case Instruction::APUT_BYTE: + case Instruction::APUT_CHAR: + case Instruction::APUT_SHORT: + case Instruction::SPUT: + case Instruction::SPUT_OBJECT: + case Instruction::SPUT_BOOLEAN: + case Instruction::SPUT_BYTE: + case Instruction::SPUT_CHAR: + case Instruction::SPUT_SHORT: + // Skip the VR containing what to store. + res = 1; + break; + case Instruction::IPUT_WIDE: + case Instruction::IPUT_WIDE_QUICK: + case Instruction::APUT_WIDE: + case Instruction::SPUT_WIDE: + // Skip the two VRs containing what to store. + res = 2; + break; + default: + // Do nothing in the general case. + break; + } + + return res; +} + +/** + * @brief Given a decoded instruction, it checks whether the instruction + * sets a constant and if it does, more information is provided about the + * constant being set. + * @param ptr_value pointer to a 64-bit holder for the constant. + * @param wide Updated by function whether a wide constant is being set by bytecode. + * @return Returns false if the decoded instruction does not represent a constant bytecode. + */ +bool MIR::DecodedInstruction::GetConstant(int64_t* ptr_value, bool* wide) const { + bool sets_const = true; + int64_t value = vB; + + DCHECK(ptr_value != nullptr); + DCHECK(wide != nullptr); + + switch (opcode) { + case Instruction::CONST_4: + case Instruction::CONST_16: + case Instruction::CONST: + *wide = false; + value <<= 32; // In order to get the sign extend. + value >>= 32; + break; + case Instruction::CONST_HIGH16: + *wide = false; + value <<= 48; // In order to get the sign extend. + value >>= 32; + break; + case Instruction::CONST_WIDE_16: + case Instruction::CONST_WIDE_32: + *wide = true; + value <<= 32; // In order to get the sign extend. + value >>= 32; + break; + case Instruction::CONST_WIDE: + *wide = true; + value = vB_wide; + break; + case Instruction::CONST_WIDE_HIGH16: + *wide = true; + value <<= 48; // In order to get the sign extend. + break; + default: + sets_const = false; + break; + } + + if (sets_const) { + *ptr_value = value; + } + + return sets_const; +} + +void BasicBlock::ResetOptimizationFlags(uint16_t reset_flags) { + // Reset flags for all MIRs in bb. + for (MIR* mir = first_mir_insn; mir != NULL; mir = mir->next) { + mir->optimization_flags &= (~reset_flags); + } +} + +void BasicBlock::Hide(CompilationUnit* c_unit) { + // First lets make it a dalvik bytecode block so it doesn't have any special meaning. + block_type = kDalvikByteCode; + + // Mark it as hidden. + hidden = true; + + // Detach it from its MIRs so we don't generate code for them. Also detached MIRs + // are updated to know that they no longer have a parent. + for (MIR* mir = first_mir_insn; mir != nullptr; mir = mir->next) { + mir->bb = NullBasicBlockId; + } + first_mir_insn = nullptr; + last_mir_insn = nullptr; + + GrowableArray::Iterator iterator(predecessors); + + MIRGraph* mir_graph = c_unit->mir_graph.get(); + while (true) { + BasicBlock* pred_bb = mir_graph->GetBasicBlock(iterator.Next()); + if (pred_bb == nullptr) { + break; + } + + // Sadly we have to go through the children by hand here. + pred_bb->ReplaceChild(id, NullBasicBlockId); + } + + // Iterate through children of bb we are hiding. + ChildBlockIterator successorChildIter(this, mir_graph); + + for (BasicBlock* childPtr = successorChildIter.Next(); childPtr != 0; childPtr = successorChildIter.Next()) { + // Replace child with null child. + childPtr->predecessors->Delete(id); + } +} + +bool BasicBlock::IsSSALiveOut(const CompilationUnit* c_unit, int ssa_reg) { + // In order to determine if the ssa reg is live out, we scan all the MIRs. We remember + // the last SSA number of the same dalvik register. At the end, if it is different than ssa_reg, + // then it is not live out of this BB. + int dalvik_reg = c_unit->mir_graph->SRegToVReg(ssa_reg); + + int last_ssa_reg = -1; + + // Walk through the MIRs backwards. + for (MIR* mir = first_mir_insn; mir != nullptr; mir = mir->next) { + // Get ssa rep. + SSARepresentation *ssa_rep = mir->ssa_rep; + + // Go through the defines for this MIR. + for (int i = 0; i < ssa_rep->num_defs; i++) { + DCHECK(ssa_rep->defs != nullptr); + + // Get the ssa reg. + int def_ssa_reg = ssa_rep->defs[i]; + + // Get dalvik reg. + int def_dalvik_reg = c_unit->mir_graph->SRegToVReg(def_ssa_reg); + + // Compare dalvik regs. + if (dalvik_reg == def_dalvik_reg) { + // We found a def of the register that we are being asked about. + // Remember it. + last_ssa_reg = def_ssa_reg; + } + } + } + + if (last_ssa_reg == -1) { + // If we get to this point we couldn't find a define of register user asked about. + // Let's assume the user knows what he's doing so we can be safe and say that if we + // couldn't find a def, it is live out. + return true; + } + + // If it is not -1, we found a match, is it ssa_reg? + return (ssa_reg == last_ssa_reg); +} + +bool BasicBlock::ReplaceChild(BasicBlockId old_bb, BasicBlockId new_bb) { + // We need to check taken, fall_through, and successor_blocks to replace. + bool found = false; + if (taken == old_bb) { + taken = new_bb; + found = true; + } + + if (fall_through == old_bb) { + fall_through = new_bb; + found = true; + } + + if (successor_block_list_type != kNotUsed) { + GrowableArray::Iterator iterator(successor_blocks); + while (true) { + SuccessorBlockInfo* successor_block_info = iterator.Next(); + if (successor_block_info == nullptr) { + break; + } + if (successor_block_info->block == old_bb) { + successor_block_info->block = new_bb; + found = true; + } + } + } + + return found; +} + +void BasicBlock::UpdatePredecessor(BasicBlockId old_parent, BasicBlockId new_parent) { + GrowableArray::Iterator iterator(predecessors); + bool found = false; + + while (true) { + BasicBlockId pred_bb_id = iterator.Next(); + + if (pred_bb_id == NullBasicBlockId) { + break; + } + + if (pred_bb_id == old_parent) { + size_t idx = iterator.GetIndex() - 1; + predecessors->Put(idx, new_parent); + found = true; + break; + } + } + + // If not found, add it. + if (found == false) { + predecessors->Insert(new_parent); + } +} + +// Create a new basic block with block_id as num_blocks_ that is +// post-incremented. +BasicBlock* MIRGraph::CreateNewBB(BBType block_type) { + BasicBlock* res = NewMemBB(block_type, num_blocks_++); + block_list_.Insert(res); + return res; +} + +void MIRGraph::CalculateBasicBlockInformation() { + PassDriverMEPostOpt driver(cu_); + driver.Launch(); +} + +void MIRGraph::InitializeBasicBlockData() { + num_blocks_ = block_list_.Size(); +} + } // namespace art diff --git a/compiler/dex/mir_graph.h b/compiler/dex/mir_graph.h index 6f8bd85630f..d990dc6bf0d 100644 --- a/compiler/dex/mir_graph.h +++ b/compiler/dex/mir_graph.h @@ -17,14 +17,31 @@ #ifndef ART_COMPILER_DEX_MIR_GRAPH_H_ #define ART_COMPILER_DEX_MIR_GRAPH_H_ +#include + #include "dex_file.h" #include "dex_instruction.h" #include "compiler_ir.h" -#include "arena_bit_vector.h" -#include "growable_array.h" +#include "invoke_type.h" +#include "mir_field_info.h" +#include "mir_method_info.h" +#include "utils/arena_bit_vector.h" +#include "utils/growable_array.h" +#include "utils/arena_containers.h" +#include "utils/scoped_arena_containers.h" +#include "reg_location.h" +#include "reg_storage.h" + +#ifdef QC_STRONG +#define QC_WEAK +#else +#define QC_WEAK __attribute__((weak)) +#endif namespace art { +class GlobalValueNumbering; + enum InstructionAnalysisAttributePos { kUninterestingOp = 0, kArithmeticOp, @@ -71,6 +88,7 @@ enum DataFlowAttributePos { kSetsConst, kFormat35c, kFormat3rc, + kFormatExtended, // Extended format for extended MIRs. kNullCheckSrc0, // Null check of uses[0]. kNullCheckSrc1, // Null check of uses[1]. kNullCheckSrc2, // Null check of uses[2]. @@ -92,41 +110,50 @@ enum DataFlowAttributePos { kRefB, kRefC, kUsesMethodStar, // Implicit use of Method*. + kUsesIField, // Accesses an instance field (IGET/IPUT). + kUsesSField, // Accesses a static field (SGET/SPUT). + kDoLVN, // Worth computing local value numbers. + kZeroDivCheck, // check for zero divider }; -#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_NOP UINT64_C(0) +#define DF_UA (UINT64_C(1) << kUA) +#define DF_UB (UINT64_C(1) << kUB) +#define DF_UC (UINT64_C(1) << kUC) +#define DF_A_WIDE (UINT64_C(1) << kAWide) +#define DF_B_WIDE (UINT64_C(1) << kBWide) +#define DF_C_WIDE (UINT64_C(1) << kCWide) +#define DF_DA (UINT64_C(1) << kDA) +#define DF_IS_MOVE (UINT64_C(1) << kIsMove) +#define DF_SETS_CONST (UINT64_C(1) << kSetsConst) +#define DF_FORMAT_35C (UINT64_C(1) << kFormat35c) +#define DF_FORMAT_3RC (UINT64_C(1) << kFormat3rc) +#define DF_FORMAT_EXTENDED (UINT64_C(1) << kFormatExtended) +#define DF_NULL_CHK_0 (UINT64_C(1) << kNullCheckSrc0) +#define DF_NULL_CHK_1 (UINT64_C(1) << kNullCheckSrc1) +#define DF_NULL_CHK_2 (UINT64_C(1) << kNullCheckSrc2) +#define DF_NULL_CHK_OUT0 (UINT64_C(1) << kNullCheckOut0) +#define DF_NON_NULL_DST (UINT64_C(1) << kDstNonNull) +#define DF_NON_NULL_RET (UINT64_C(1) << kRetNonNull) +#define DF_NULL_TRANSFER_0 (UINT64_C(1) << kNullTransferSrc0) +#define DF_NULL_TRANSFER_N (UINT64_C(1) << kNullTransferSrcN) +#define DF_RANGE_CHK_1 (UINT64_C(1) << kRangeCheckSrc1) +#define DF_RANGE_CHK_2 (UINT64_C(1) << kRangeCheckSrc2) +#define DF_RANGE_CHK_3 (UINT64_C(1) << kRangeCheckSrc3) +#define DF_FP_A (UINT64_C(1) << kFPA) +#define DF_FP_B (UINT64_C(1) << kFPB) +#define DF_FP_C (UINT64_C(1) << kFPC) +#define DF_CORE_A (UINT64_C(1) << kCoreA) +#define DF_CORE_B (UINT64_C(1) << kCoreB) +#define DF_CORE_C (UINT64_C(1) << kCoreC) +#define DF_REF_A (UINT64_C(1) << kRefA) +#define DF_REF_B (UINT64_C(1) << kRefB) +#define DF_REF_C (UINT64_C(1) << kRefC) +#define DF_UMS (UINT64_C(1) << kUsesMethodStar) +#define DF_IFIELD (UINT64_C(1) << kUsesIField) +#define DF_SFIELD (UINT64_C(1) << kUsesSField) +#define DF_LVN (UINT64_C(1) << kDoLVN) +#define DF_ZERO_DIV_CHECK (UINT64_C(1) << kZeroDivCheck) #define DF_HAS_USES (DF_UA | DF_UB | DF_UC) @@ -149,7 +176,7 @@ enum DataFlowAttributePos { #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) - +#define DF_NULL_TRANSFER (DF_NULL_TRANSFER_0 | DF_NULL_TRANSFER_N) enum OatMethodAttributes { kIsLeaf, // Method is leaf. kHasLoop, // Method contains simple loop. @@ -163,26 +190,26 @@ enum OatMethodAttributes { #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_IGNORE_CLINIT_CHECK (1 << kMIRIgnoreClInitCheck) #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_IGNORE_ZERO_DIV_CHECK (1 << kMIRIgnoreZeroDivCheck) #define MIR_DUP (1 << kMIRDup) #define BLOCK_NAME_LEN 80 +typedef uint16_t BasicBlockId; +static const BasicBlockId NullBasicBlockId = 0; +static constexpr bool kLeafOptimization = false; + /* * 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 @@ -190,15 +217,21 @@ enum OatMethodAttributes { * name of compiler-introduced temporaries. */ struct CompilerTemp { - int s_reg; + int32_t v_reg; // Virtual register number for temporary. + int32_t s_reg_low; // SSA name for low Dalvik word. +}; + +enum CompilerTempType { + kCompilerTempVR, // A virtual register temporary. + kCompilerTempSpecialMethodPtr, // Temporary that keeps track of current method pointer. }; // 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; + int32_t null_checks; + int32_t null_checks_eliminated; + int32_t range_checks; + int32_t range_checks_eliminated; }; // Dataflow attributes of a basic block. @@ -207,8 +240,8 @@ struct BasicBlockDataFlow { ArenaBitVector* def_v; ArenaBitVector* live_in_v; ArenaBitVector* phi_v; - int* vreg_to_ssa_map; - ArenaBitVector* ending_null_check_v; + int32_t* vreg_to_ssa_map_exit; + ArenaBitVector* ending_check_v; // For null check and class init check elimination. }; /* @@ -218,102 +251,277 @@ struct BasicBlockDataFlow { * 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. + * + * TODO: + * 1. Add accessors for uses/defs and make data private + * 2. Change fp_use/fp_def to a bit array (could help memory usage) + * 3. Combine array storage into internal array and handled via accessors from 1. */ struct SSARepresentation { - int num_uses; - int* uses; + int32_t* uses; bool* fp_use; - int num_defs; - int* defs; + int32_t* defs; bool* fp_def; + int16_t num_uses_allocated; + int16_t num_defs_allocated; + int16_t num_uses; + int16_t num_defs; + + static uint32_t GetStartUseIndex(Instruction::Code opcode); }; +struct ExtendedMIR; + /* * The Midlevel Intermediate Representation node, which may be largely considered a * wrapper around a Dalvik byte code. */ struct MIR { - DecodedInstruction dalvikInsn; - uint32_t width; // NOTE: only need 16 bits for width. - unsigned int offset; - int m_unit_index; // From which method was this MIR included - MIR* prev; + /* + * TODO: remove embedded DecodedInstruction to save space, keeping only opcode. Recover + * additional fields on as-needed basis. Question: how to support MIR Pseudo-ops; probably + * need to carry aux data pointer. + */ + struct DecodedInstruction { + uint32_t vA; + uint32_t vB; + uint64_t vB_wide; /* for k51l */ + uint32_t vC; + uint32_t arg[5]; /* vC/D/E/F/G in invoke or filled-new-array */ + Instruction::Code opcode; + + explicit DecodedInstruction():vA(0), vB(0), vB_wide(0), vC(0), opcode(Instruction::NOP) { + } + + /* + * Given a decoded instruction representing a const bytecode, it updates + * the out arguments with proper values as dictated by the constant bytecode. + */ + bool GetConstant(int64_t* ptr_value, bool* wide) const; + + static bool IsPseudoMirOp(Instruction::Code opcode) { + return static_cast(opcode) >= static_cast(kMirOpFirst); + } + + static bool IsPseudoMirOp(int opcode) { + return opcode >= static_cast(kMirOpFirst); + } + + bool IsInvoke() const { + return !IsPseudoMirOp(opcode) && ((Instruction::FlagsOf(opcode) & Instruction::kInvoke) == Instruction::kInvoke); + } + + bool IsStore() const { + return !IsPseudoMirOp(opcode) && ((Instruction::FlagsOf(opcode) & Instruction::kStore) == Instruction::kStore); + } + + bool IsLoad() const { + return !IsPseudoMirOp(opcode) && ((Instruction::FlagsOf(opcode) & Instruction::kLoad) == Instruction::kLoad); + } + + bool IsConditionalBranch() const { + return !IsPseudoMirOp(opcode) && (Instruction::FlagsOf(opcode) == (Instruction::kContinue | Instruction::kBranch)); + } + + /** + * @brief Is the register C component of the decoded instruction a constant? + */ + bool IsCFieldOrConstant() const { + return !IsPseudoMirOp(opcode) && ((Instruction::FlagsOf(opcode) & Instruction::kRegCFieldOrConstant) == Instruction::kRegCFieldOrConstant); + } + + /** + * @brief Is the register C component of the decoded instruction a constant? + */ + bool IsBFieldOrConstant() const { + return !IsPseudoMirOp(opcode) && ((Instruction::FlagsOf(opcode) & Instruction::kRegBFieldOrConstant) == Instruction::kRegBFieldOrConstant); + } + + bool IsCast() const { + return !IsPseudoMirOp(opcode) && ((Instruction::FlagsOf(opcode) & Instruction::kCast) == Instruction::kCast); + } + + /** + * @brief Does the instruction clobber memory? + * @details Clobber means that the instruction changes the memory not in a punctual way. + * Therefore any supposition on memory aliasing or memory contents should be disregarded + * when crossing such an instruction. + */ + bool Clobbers() const { + return !IsPseudoMirOp(opcode) && ((Instruction::FlagsOf(opcode) & Instruction::kClobber) == Instruction::kClobber); + } + + bool IsLinear() const { + return !IsPseudoMirOp(opcode) && (Instruction::FlagsOf(opcode) & (Instruction::kAdd | Instruction::kSubtract)) != 0; + } + } dalvikInsn; + + NarrowDexOffset offset; // Offset of the instruction in code units. + uint16_t optimization_flags; + int16_t m_unit_index; // From which method was this MIR included + BasicBlockId bb; MIR* next; SSARepresentation* ssa_rep; - int optimization_flags; union { - // Establish link between two halves of throwing instructions. + // Incoming edges for phi node. + BasicBlockId* phi_incoming; + // Establish link from check instruction (kMirOpCheck) to the actual throwing instruction. MIR* throw_insn; - // Saved opcode for NOP'd MIRs - Instruction::Code original_opcode; + // Branch condition for fused cmp or select. + ConditionCode ccode; + // IGET/IPUT lowering info index, points to MIRGraph::ifield_lowering_infos_. Due to limit on + // the number of code points (64K) and size of IGET/IPUT insn (2), this will never exceed 32K. + uint32_t ifield_lowering_info; + // SGET/SPUT lowering info index, points to MIRGraph::sfield_lowering_infos_. Due to limit on + // the number of code points (64K) and size of SGET/SPUT insn (2), this will never exceed 32K. + uint32_t sfield_lowering_info; + // INVOKE data index, points to MIRGraph::method_lowering_infos_. + uint32_t method_lowering_info; } meta; + + explicit MIR():offset(0), optimization_flags(0), m_unit_index(0), bb(NullBasicBlockId), + next(nullptr), ssa_rep(nullptr) , extraData(nullptr){ + memset(&meta, 0, sizeof(meta)); + } + + uint32_t GetStartUseIndex() const { + return SSARepresentation::GetStartUseIndex(dalvikInsn.opcode); + } + + MIR* Copy(CompilationUnit *c_unit); + MIR* Copy(MIRGraph* mir_Graph); + + static void* operator new(size_t size, ArenaAllocator* arena) { + return arena->Alloc(sizeof(MIR), kArenaAllocMIR); + } + static void operator delete(void* p) {} // Nop. + + ExtendedMIR* extraData; + }; 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; + BasicBlockId id; + BasicBlockId dfs_id; + NarrowDexOffset start_offset; // Offset in code units. + BasicBlockId fall_through; + BasicBlockId taken; + BasicBlockId i_dom; // Immediate dominator. uint16_t nesting_depth; - BBType block_type; + BBType block_type:4; + BlockListType successor_block_list_type:4; + bool visited:1; + bool hidden:1; + bool catch_entry:1; + bool explicit_throw:1; + bool conditional_branch:1; + bool terminated_by_return:1; // Block ends with a Dalvik return opcode. + bool dominates_return:1; // Is a member of return extended basic block. + bool use_lvn:1; // Run local value numbering on this block. 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; + GrowableArray* predecessors; + GrowableArray* successor_blocks; + + void AppendMIR(MIR* mir); + void AppendMIRList(MIR* first_list_mir, MIR* last_list_mir); + void AppendMIRList(const std::vector& insns); + void PrependMIR(MIR* mir); + void PrependMIRList(MIR* first_list_mir, MIR* last_list_mir); + void PrependMIRList(const std::vector& to_add); + void InsertMIRAfter(MIR* current_mir, MIR* new_mir); + void InsertMIRListAfter(MIR* insert_after, MIR* first_list_mir, MIR* last_list_mir); + MIR* FindPreviousMIR(MIR* mir); + void InsertMIRBefore(MIR* insert_before, MIR* list); + void InsertMIRListBefore(MIR* insert_before, MIR* first_list_mir, MIR* last_list_mir); + bool RemoveMIR(MIR* mir); + bool RemoveMIRList(MIR* first_list_mir, MIR* last_list_mir); + + BasicBlock* Copy(CompilationUnit* c_unit); + BasicBlock* Copy(MIRGraph* mir_graph); + + /** + * @brief Reset the optimization_flags field of each MIR. + */ + void ResetOptimizationFlags(uint16_t reset_flags); + + /** + * @brief Hide the BasicBlock. + * @details Set it to kDalvikByteCode, set hidden to true, remove all MIRs, + * remove itself from any predecessor edges, remove itself from any + * child's predecessor growable array. + */ + void Hide(CompilationUnit* c_unit); + + /** + * @brief Is ssa_reg the last SSA definition of that VR in the block? + */ + bool IsSSALiveOut(const CompilationUnit* c_unit, int ssa_reg); + + /** + * @brief Replace the edge going to old_bb to now go towards new_bb. + */ + bool ReplaceChild(BasicBlockId old_bb, BasicBlockId new_bb); + + /** + * @brief Update the predecessor growable array from old_pred to new_pred. + */ + void UpdatePredecessor(BasicBlockId old_pred, BasicBlockId new_pred); + + /** + * @brief Used to obtain the next MIR that follows unconditionally. + * @details The implementation does not guarantee that a MIR does not + * follow even if this method returns nullptr. + * @param mir_graph the MIRGraph. + * @param current The MIR for which to find an unconditional follower. + * @return Returns the following MIR if one can be found. + */ + MIR* GetNextUnconditionalMir(MIRGraph* mir_graph, MIR* current); + bool IsExceptionBlock() const; + + static void* operator new(size_t size, ArenaAllocator* arena) { + return arena->Alloc(sizeof(BasicBlock), kArenaAllocBB); + } + static void operator delete(void* p) {} // Nop. }; /* * 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 + * "SuccessorBlockInfo". For catch blocks, key is type index for the exception. For switch * blocks, key is the case value. */ -// TODO: make class with placement new. struct SuccessorBlockInfo { - BasicBlock* block; + BasicBlockId 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. +/** + * @class ChildBlockIterator + * @brief Enable an easy iteration of the children. */ -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. +class ChildBlockIterator { + public: + /** + * @brief Constructs a child iterator. + * @param bb The basic whose children we need to iterate through. + * @param mir_graph The MIRGraph used to get the basic block during iteration. + */ + ChildBlockIterator(BasicBlock* bb, MIRGraph* mir_graph); + BasicBlock* Next(); + + private: + BasicBlock* basic_block_; + MIRGraph* mir_graph_; + bool visited_fallthrough_; + bool visited_taken_; + bool have_successors_; + GrowableArray::Iterator successor_iter_; }; /* @@ -334,12 +542,16 @@ struct CallInfo { RegLocation target; // Target of following move_result. bool skip_this; bool is_range; - int offset; // Dalvik offset. + DexOffset offset; // Offset in code units. + MIR* mir; }; -const RegLocation bad_loc = {kLocDalvikFrame, 0, 0, 0, 0, 0, 0, 0, 0, - INVALID_REG, INVALID_REG, INVALID_SREG, INVALID_SREG}; +const RegLocation bad_loc = {kLocDalvikFrame, 0, 0, 0, 0, 0, 0, 0, 0, RegStorage(), INVALID_SREG, + INVALID_SREG}; + + +class QCMIRGraph; class MIRGraph { public: @@ -350,7 +562,12 @@ class MIRGraph { * Examine the graph to determine whether it's worthwile to spend the time compiling * this method. */ - bool SkipCompilation(Runtime::CompilerFilter compiler_filter); + bool SkipCompilation(std::string* skip_message); + + /* + * Should we skip the compilation of this method based on its name? + */ + bool SkipCompilationByName(const std::string& methodname); /* * Parse dex method and add MIR at current insert point. Returns id (which is @@ -361,7 +578,7 @@ class MIRGraph { uint32_t method_idx, jobject class_loader, const DexFile& dex_file); /* Find existing block */ - BasicBlock* FindBlock(unsigned int code_offset) { + BasicBlock* FindBlock(DexOffset code_offset) { return FindBlock(code_offset, false, false, NULL); } @@ -373,7 +590,7 @@ class MIRGraph { return m_units_[m_unit_index]->GetCodeItem()->insns_; } - int GetNumBlocks() const { + unsigned int GetNumBlocks() const { return num_blocks_; } @@ -393,8 +610,8 @@ class MIRGraph { return exit_block_; } - BasicBlock* GetBasicBlock(int block_id) const { - return block_list_.Get(block_id); + BasicBlock* GetBasicBlock(unsigned int block_id) const { + return (block_id == NullBasicBlockId) ? NULL : block_list_.Get(block_id); } size_t GetBasicBlockListCount() const { @@ -405,15 +622,15 @@ class MIRGraph { return &block_list_; } - GrowableArray* GetDfsOrder() { + GrowableArray* GetDfsOrder() { return dfs_order_; } - GrowableArray* GetDfsPostOrder() { + GrowableArray* GetDfsPostOrder() { return dfs_post_order_; } - GrowableArray* GetDomPostOrder() { + GrowableArray* GetDomPostOrder() { return dom_post_order_traversal_; } @@ -427,7 +644,7 @@ class MIRGraph { void EnableOpcodeCounting() { opcode_count_ = static_cast(arena_->Alloc(kNumPackedOpcodes * sizeof(int), - ArenaAllocator::kAllocMisc)); + kArenaAllocMisc)); } void ShowOpcodeStats(); @@ -436,20 +653,82 @@ class MIRGraph { return m_units_[current_method_]; } - void DumpCFG(const char* dir_prefix, bool all_blocks); + /** + * @brief Dump a CFG into a dot file format. + * @param dir_prefix the directory the file will be created in. + * @param all_blocks does the dumper use all the basic blocks or use the reachable blocks. + * @param suffix does the filename require a suffix or not (default = nullptr). + */ + void DumpCFG(const char* dir_prefix, bool all_blocks, const char* suffix = nullptr); - void BuildRegLocations(); + bool HasFieldAccess() const { + return (merged_df_flags_ & (DF_IFIELD | DF_SFIELD)) != 0u; + } + + bool HasStaticFieldAccess() const { + return (merged_df_flags_ & DF_SFIELD) != 0u; + } + + bool HasInvokes() const { + // NOTE: These formats include the rare filled-new-array/range. + return (merged_df_flags_ & (DF_FORMAT_35C | DF_FORMAT_3RC)) != 0u; + } + + void DoCacheFieldLoweringInfo(); + + const MirIFieldLoweringInfo& GetIFieldLoweringInfo(MIR* mir) const { + DCHECK_LT(mir->meta.ifield_lowering_info, ifield_lowering_infos_.Size()); + return ifield_lowering_infos_.GetRawStorage()[mir->meta.ifield_lowering_info]; + } + + const MirSFieldLoweringInfo& GetSFieldLoweringInfo(MIR* mir) const { + DCHECK_LT(mir->meta.sfield_lowering_info, sfield_lowering_infos_.Size()); + return sfield_lowering_infos_.GetRawStorage()[mir->meta.sfield_lowering_info]; + } + + void DoCacheMethodLoweringInfo(); + + const MirMethodLoweringInfo& GetMethodLoweringInfo(MIR* mir) { + DCHECK_LT(mir->meta.method_lowering_info, method_lowering_infos_.Size()); + return method_lowering_infos_.GetRawStorage()[mir->meta.method_lowering_info]; + } + + void ComputeInlineIFieldLoweringInfo(uint16_t field_idx, MIR* invoke, MIR* iget_or_iput); + + void InitRegLocations(); + + void RemapRegLocations(); void DumpRegLocTable(RegLocation* table, int count); void BasicBlockOptimization(); + GrowableArray* GetTopologicalSortOrder() { + DCHECK(topological_order_ != nullptr); + return topological_order_; + } + + GrowableArray* GetTopologicalSortOrderLoopEnds() { + DCHECK(topological_order_loop_ends_ != nullptr); + return topological_order_loop_ends_; + } + + GrowableArray* GetTopologicalSortOrderIndexes() { + DCHECK(topological_order_indexes_ != nullptr); + return topological_order_indexes_; + } + + GrowableArray>* GetTopologicalSortOrderLoopHeadStack() { + DCHECK(topological_order_loop_head_stack_ != nullptr); + return topological_order_loop_head_stack_; + } + bool IsConst(int32_t s_reg) const { return is_constant_v_->IsBitSet(s_reg); } bool IsConst(RegLocation loc) const { - return (IsConst(loc.orig_sreg)); + return loc.orig_sreg < 0 ? false : IsConst(loc.orig_sreg); } int32_t ConstantValue(RegLocation loc) const { @@ -464,10 +743,18 @@ class MIRGraph { int64_t ConstantValueWide(RegLocation loc) const { DCHECK(IsConst(loc)); + DCHECK(!loc.high_word); // Do not allow asking for the high partner. + DCHECK_LT(loc.orig_sreg + 1, GetNumSSARegs()); return (static_cast(constant_values_[loc.orig_sreg + 1]) << 32) | Low32Bits(static_cast(constant_values_[loc.orig_sreg])); } + int64_t ConstantValueWide(int32_t s_reg) const { + DCHECK(IsConst(s_reg)); + return (static_cast(constant_values_[s_reg + 1]) << 32) | + Low32Bits(static_cast(constant_values_[s_reg])); + } + bool IsConstantNullRef(RegLocation loc) const { return loc.ref && loc.is_const && (ConstantValue(loc) == 0); } @@ -477,6 +764,12 @@ class MIRGraph { } void SetNumSSARegs(int new_num) { + /* + * TODO: It's theoretically possible to exceed 32767, though any cases which did + * would be filtered out with current settings. When orig_sreg field is removed + * from RegLocation, expand s_reg_low to handle all possible cases and remove DCHECK(). + */ + CHECK_EQ(new_num, static_cast(new_num)); num_ssa_regs_ = new_num; } @@ -536,16 +829,82 @@ class MIRGraph { return bad_loc; } - int GetMethodSReg() { + int GetMethodSReg() const { return method_sreg_; } + /** + * @brief Used to obtain the number of compiler temporaries being used. + * @return Returns the number of compiler temporaries. + */ + size_t GetNumUsedCompilerTemps() const { + size_t total_num_temps = compiler_temps_.Size(); + DCHECK_LE(num_non_special_compiler_temps_, total_num_temps); + return total_num_temps; + } + + /** + * @brief Used to obtain the number of non-special compiler temporaries being used. + * @return Returns the number of non-special compiler temporaries. + */ + size_t GetNumNonSpecialCompilerTemps() const { + return num_non_special_compiler_temps_; + } + + /** + * @brief Used to set the total number of available non-special compiler temporaries. + * @details Can fail setting the new max if there are more temps being used than the new_max. + * @param new_max The new maximum number of non-special compiler temporaries. + * @return Returns true if the max was set and false if failed to set. + */ + bool SetMaxAvailableNonSpecialCompilerTemps(size_t new_max) { + if (new_max < GetNumNonSpecialCompilerTemps()) { + return false; + } else { + max_available_non_special_compiler_temps_ = new_max; + return true; + } + } + + /** + * @brief Provides the number of non-special compiler temps available. + * @details Even if this returns zero, special compiler temps are guaranteed to be available. + * @return Returns the number of available temps. + */ + size_t GetNumAvailableNonSpecialCompilerTemps(); + + /** + * @brief Used to obtain an existing compiler temporary. + * @param index The index of the temporary which must be strictly less than the + * number of temporaries. + * @return Returns the temporary that was asked for. + */ + CompilerTemp* GetCompilerTemp(size_t index) const { + return compiler_temps_.Get(index); + } + + /** + * @brief Used to obtain the maximum number of compiler temporaries that can be requested. + * @return Returns the maximum number of compiler temporaries, whether used or not. + */ + size_t GetMaxPossibleCompilerTemps() const { + return max_available_special_compiler_temps_ + max_available_non_special_compiler_temps_; + } + + /** + * @brief Used to obtain a new unique compiler temporary. + * @param ct_type Type of compiler temporary requested. + * @param wide Whether we should allocate a wide temporary. + * @return Returns the newly created compiler temporary. + */ + CompilerTemp* GetNewCompilerTemp(CompilerTempType ct_type, bool wide); + bool MethodIsLeaf() { return attributes_ & METHOD_IS_LEAF; } RegLocation GetRegLocation(int index) { - DCHECK((index >= 0) && (index > num_ssa_regs_)); + DCHECK((index >= 0) && (index < num_ssa_regs_)); return reg_location_[index]; } @@ -553,41 +912,82 @@ class MIRGraph { return reg_location_[method_sreg_]; } - bool IsSpecialCase() { - return special_case_ != kNoHandler; + bool IsBackedge(BasicBlock* branch_bb, BasicBlockId target_bb_id) { + return ((target_bb_id != NullBasicBlockId) && + (GetBasicBlock(target_bb_id)->start_offset <= branch_bb->start_offset)); } - SpecialCaseHandler GetSpecialCase() { - return special_case_; + bool IsBackwardsBranch(BasicBlock* branch_bb) { + return IsBackedge(branch_bb, branch_bb->taken) || IsBackedge(branch_bb, branch_bb->fall_through); } - bool IsBackedge(BasicBlock* branch_bb, BasicBlock* target_bb) { - return ((target_bb != NULL) && (target_bb->start_offset <= branch_bb->start_offset)); + void CountBranch(DexOffset target_offset) { + if (target_offset <= current_offset_) { + backward_branches_++; + } else { + forward_branches_++; + } } - bool IsBackwardsBranch(BasicBlock* branch_bb) { - return IsBackedge(branch_bb, branch_bb->taken) || IsBackedge(branch_bb, branch_bb->fall_through); + int GetBranchCount() { + return backward_branches_ + forward_branches_; + } + + // Is this vreg in the in set? + bool IsInVReg(int vreg) { + return (vreg >= cu_->num_regs); } - 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(); + void EliminateNullChecksAndInferTypesStart(); + bool EliminateNullChecksAndInferTypes(BasicBlock* bb); + void EliminateNullChecksAndInferTypesEnd(); + bool EliminateClassInitChecksGate(); + bool EliminateClassInitChecks(BasicBlock* bb); + void EliminateClassInitChecksEnd(); + bool ApplyGlobalValueNumberingGate(); + bool ApplyGlobalValueNumbering(BasicBlock* bb); + void ApplyGlobalValueNumberingEnd(); + /* + * Type inference handling helpers. Because Dalvik's bytecode is not fully typed, + * we have to do some work to figure out the sreg type. For some operations it is + * clear based on the opcode (i.e. ADD_FLOAT v0, v1, v2), but for others (MOVE), we + * may never know the "real" type. + * + * We perform the type inference operation by using an iterative walk over + * the graph, propagating types "defined" by typed opcodes to uses and defs in + * non-typed opcodes (such as MOVE). The Setxx(index) helpers are used to set defined + * types on typed opcodes (such as ADD_INT). The Setxx(index, is_xx) form is used to + * propagate types through non-typed opcodes such as PHI and MOVE. The is_xx flag + * tells whether our guess of the type is based on a previously typed definition. + * If so, the defined type takes precedence. Note that it's possible to have the same sreg + * show multiple defined types because dx treats constants as untyped bit patterns. + * The return value of the Setxx() helpers says whether or not the Setxx() action changed + * the current guess, and is used to know when to terminate the iterative walk. + */ bool SetFp(int index, bool is_fp); + bool SetFp(int index); bool SetCore(int index, bool is_core); + bool SetCore(int index); bool SetRef(int index, bool is_ref); + bool SetRef(int index); bool SetWide(int index, bool is_wide); + bool SetWide(int index); 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); + bool SetHigh(int index); + + bool PuntToInterpreter() { + return punt_to_interpreter_; + } + + void SetPuntToInterpreter(bool val) { + punt_to_interpreter_ = val; + } + char* GetDalvikDisassembly(const MIR* mir); void ReplaceSpecialChars(std::string& str); std::string GetSSAName(int ssa_reg); @@ -597,6 +997,78 @@ class MIRGraph { void DumpMIRGraph(); CallInfo* NewMemCallInfo(BasicBlock* bb, MIR* mir, InvokeType type, bool is_range); BasicBlock* NewMemBB(BBType block_type, int block_id); + MIR* NewMIR(); + MIR* AdvanceMIR(BasicBlock** p_bb, MIR* mir); + BasicBlock* NextDominatedBlock(BasicBlock* bb); + bool LayoutBlocks(BasicBlock* bb); + void ComputeTopologicalSortOrder(); + BasicBlock* CreateNewBB(BBType block_type); + + bool InlineSpecialMethodsGate(); + void InlineSpecialMethodsStart(); + void InlineSpecialMethods(BasicBlock* bb); + void InlineSpecialMethodsEnd(); + + /** + * @brief Perform the initial preparation for the Method Uses. + */ + void InitializeMethodUses(); + + /** + * @brief Perform the initial preparation for the Constant Propagation. + */ + void InitializeConstantPropagation(); + + /** + * @brief Perform the initial preparation for the SSA Transformation. + */ + void SSATransformationStart(); + + /** + * @brief Insert a the operands for the Phi nodes. + * @param bb the considered BasicBlock. + * @return true + */ + bool InsertPhiNodeOperands(BasicBlock* bb); + + /** + * @brief Perform the cleanup after the SSA Transformation. + */ + void SSATransformationEnd(); + + /** + * @brief Perform constant propagation on a BasicBlock. + * @param bb the considered BasicBlock. + */ + void DoConstantPropagation(BasicBlock* bb); + + /** + * @brief Count the uses in the BasicBlock + * @param bb the BasicBlock + */ + void CountUses(struct BasicBlock* bb); + + static uint64_t GetDataFlowAttributes(Instruction::Code opcode) QC_WEAK; + static uint64_t GetDataFlowAttributes(MIR* mir); + + /** + * @brief Combine BasicBlocks + * @param the BasicBlock we are considering + */ + void CombineBlocks(BasicBlock* bb); + + void ClearAllVisitedFlags(); + + void AllocateSSAUseData(MIR *mir, int num_uses); + void AllocateSSADefData(MIR *mir, int num_defs); + void CalculateBasicBlockInformation(); + void InitializeBasicBlockData(); + void ComputeDFSOrders(); + void ComputeDefBlockMatrix(); + void ComputeDominators(); + void CompilerInitializeSSAConversion(); + void InsertPhiNodes(); + void DoDFSPreOrderSSARename(BasicBlock* block); /* * IsDebugBuild sanity check: keep track of the Dex PCs for catch entries so that later on @@ -605,63 +1077,73 @@ class MIRGraph { 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. + RegLocation* reg_location_; // Map SSA names to location. + ArenaSafeMap block_id_map_; // Block collapse lookup cache. - static const int oat_data_flow_attributes_[kMirOpLast]; static const char* extended_mir_op_names_[kMirOpLast - kMirOpFirst]; static const uint32_t analysis_attributes_[kMirOpLast]; - private: + static const char * GetExtendedMirOpName(int index) QC_WEAK; + void HandleSSADef(int* defs, int dalvik_reg, int reg_index); + bool InferTypeAndSize(BasicBlock* bb, MIR* mir, bool changed); + + // Used for removing redudant suspend tests + void AppendGenSuspendTestList(BasicBlock* bb) { + if (gen_suspend_test_list_.Size() == 0 || + gen_suspend_test_list_.Get(gen_suspend_test_list_.Size() - 1) != bb) { + gen_suspend_test_list_.Insert(bb); + } + } + + /* This is used to check if there is already a method call dominating the + * source basic block of a backedge and being dominated by the target basic + * block of the backedge. + */ + bool HasSuspendTestBetween(BasicBlock* source, BasicBlockId target_id); + + protected: 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(); + void HandleExtended(ArenaBitVector* use_v, ArenaBitVector* def_v, + ArenaBitVector* live_in_v, + const MIR::DecodedInstruction& d_insn); bool DoSSAConversion(BasicBlock* bb); bool InvokeUsesMethodStar(MIR* mir); - int ParseInsn(const uint16_t* code_ptr, DecodedInstruction* decoded_instruction); + int ParseInsn(const uint16_t* code_ptr, MIR::DecodedInstruction* decoded_instruction); bool ContentIsInsn(const uint16_t* code_ptr); - BasicBlock* SplitBlock(unsigned int code_offset, BasicBlock* orig_block, + BasicBlock* SplitBlock(DexOffset code_offset, BasicBlock* orig_block, BasicBlock** immed_pred_block_p); - BasicBlock* FindBlock(unsigned int code_offset, bool split, bool create, + BasicBlock* FindBlock(DexOffset 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, + bool IsBadMonitorExitCatch(NarrowDexOffset monitor_exit_offset, NarrowDexOffset catch_offset); + BasicBlock* ProcessCanBranch(BasicBlock* cur_block, MIR* insn, DexOffset 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, + BasicBlock* ProcessCanSwitch(BasicBlock* cur_block, MIR* insn, DexOffset cur_offset, int width, + int flags); + BasicBlock* ProcessCanThrow(BasicBlock* cur_block, MIR* insn, DexOffset 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); + void DataFlowSSAFormatExtended(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); @@ -669,13 +1151,12 @@ class MIRGraph { 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); void AnalyzeBlock(BasicBlock* bb, struct MethodStats* stats); - bool ComputeSkipCompilation(struct MethodStats* stats, bool skip_default); + bool ComputeSkipCompilation(struct MethodStats* stats, bool skip_default, + std::string* skip_message); CompilationUnit* const cu_; GrowableArray* ssa_base_vregs_; @@ -689,37 +1170,71 @@ class MIRGraph { 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_; + unsigned int max_num_reachable_blocks_; + GrowableArray* dfs_order_; + GrowableArray* dfs_post_order_; + GrowableArray* dom_post_order_traversal_; + GrowableArray* topological_order_; + // Indexes in topological_order_ need to be only as big as the BasicBlockId. + COMPILE_ASSERT(sizeof(BasicBlockId) == sizeof(uint16_t), assuming_16_bit_BasicBlockId); + // For each loop head, remember the past-the-end index of the end of the loop. 0 if not loop head. + GrowableArray* topological_order_loop_ends_; + // Map BB ids to topological_order_ indexes. 0xffff if not included (hidden or null block). + GrowableArray* topological_order_indexes_; + // Stack of the loop head indexes and recalculation flags for RepeatingTopologicalSortIterator. + GrowableArray>* topological_order_loop_head_stack_; 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. + std::unique_ptr temp_scoped_alloc_; + uint16_t* temp_insn_data_; + uint32_t temp_bit_vector_size_; + ArenaBitVector* temp_bit_vector_; + std::unique_ptr temp_gvn_; static const int kInvalidEntry = -1; GrowableArray block_list_; ArenaBitVector* try_block_addr_; BasicBlock* entry_block_; BasicBlock* exit_block_; - BasicBlock* cur_block_; - int num_blocks_; + unsigned 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 + GrowableArray dex_pc_to_block_map_; // FindBlock lookup cache. + ArenaVector 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 + ArenaVector method_stack_; // Include stack int current_method_; - int current_offset_; + DexOffset current_offset_; // Offset in code units 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". + ArenaVector extended_basic_blocks_; // Heads of block "traces". int method_sreg_; unsigned int attributes_; Checkstats* checkstats_; - SpecialCaseHandler special_case_; ArenaAllocator* arena_; + int backward_branches_; + int forward_branches_; + GrowableArray compiler_temps_; + size_t num_non_special_compiler_temps_; + size_t max_available_non_special_compiler_temps_; + size_t max_available_special_compiler_temps_; + bool punt_to_interpreter_; // Difficult or not worthwhile - just interpret. + uint64_t merged_df_flags_; + GrowableArray ifield_lowering_infos_; + GrowableArray sfield_lowering_infos_; + GrowableArray method_lowering_infos_; + static const uint64_t oat_data_flow_attributes_[kMirOpLast]; + GrowableArray gen_suspend_test_list_; // List of blocks containing suspend tests + + friend class ClassInitCheckEliminationTest; + friend class GlobalValueNumberingTest; + friend class LocalValueNumberingTest; + friend class TopologicalSortOrderTest; + + friend class QCMIRGraph; + + public: + QCMIRGraph* qcm; + }; } // namespace art diff --git a/compiler/dex/mir_graph_test.cc b/compiler/dex/mir_graph_test.cc new file mode 100644 index 00000000000..932f453b3c3 --- /dev/null +++ b/compiler/dex/mir_graph_test.cc @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mir_graph.h" +#include "gtest/gtest.h" + +namespace art { + +class TopologicalSortOrderTest : public testing::Test { + protected: + struct BBDef { + static constexpr size_t kMaxSuccessors = 4; + static constexpr size_t kMaxPredecessors = 4; + + BBType type; + size_t num_successors; + BasicBlockId successors[kMaxPredecessors]; + size_t num_predecessors; + BasicBlockId predecessors[kMaxPredecessors]; + }; + +#define DEF_SUCC0() \ + 0u, { } +#define DEF_SUCC1(s1) \ + 1u, { s1 } +#define DEF_SUCC2(s1, s2) \ + 2u, { s1, s2 } +#define DEF_SUCC3(s1, s2, s3) \ + 3u, { s1, s2, s3 } +#define DEF_SUCC4(s1, s2, s3, s4) \ + 4u, { s1, s2, s3, s4 } +#define DEF_PRED0() \ + 0u, { } +#define DEF_PRED1(p1) \ + 1u, { p1 } +#define DEF_PRED2(p1, p2) \ + 2u, { p1, p2 } +#define DEF_PRED3(p1, p2, p3) \ + 3u, { p1, p2, p3 } +#define DEF_PRED4(p1, p2, p3, p4) \ + 4u, { p1, p2, p3, p4 } +#define DEF_BB(type, succ, pred) \ + { type, succ, pred } + + void DoPrepareBasicBlocks(const BBDef* defs, size_t count) { + cu_.mir_graph->block_id_map_.clear(); + cu_.mir_graph->block_list_.Reset(); + ASSERT_LT(3u, count); // null, entry, exit and at least one bytecode block. + ASSERT_EQ(kNullBlock, defs[0].type); + ASSERT_EQ(kEntryBlock, defs[1].type); + ASSERT_EQ(kExitBlock, defs[2].type); + for (size_t i = 0u; i != count; ++i) { + const BBDef* def = &defs[i]; + BasicBlock* bb = cu_.mir_graph->NewMemBB(def->type, i); + cu_.mir_graph->block_list_.Insert(bb); + if (def->num_successors <= 2) { + bb->successor_block_list_type = kNotUsed; + bb->successor_blocks = nullptr; + bb->fall_through = (def->num_successors >= 1) ? def->successors[0] : 0u; + bb->taken = (def->num_successors >= 2) ? def->successors[1] : 0u; + } else { + bb->successor_block_list_type = kPackedSwitch; + bb->fall_through = 0u; + bb->taken = 0u; + bb->successor_blocks = new (&cu_.arena) GrowableArray( + &cu_.arena, def->num_successors, kGrowableArraySuccessorBlocks); + for (size_t j = 0u; j != def->num_successors; ++j) { + SuccessorBlockInfo* successor_block_info = + static_cast(cu_.arena.Alloc(sizeof(SuccessorBlockInfo), + kArenaAllocSuccessor)); + successor_block_info->block = j; + successor_block_info->key = 0u; // Not used by class init check elimination. + bb->successor_blocks->Insert(successor_block_info); + } + } + bb->predecessors = new (&cu_.arena) GrowableArray( + &cu_.arena, def->num_predecessors, kGrowableArrayPredecessors); + for (size_t j = 0u; j != def->num_predecessors; ++j) { + ASSERT_NE(0u, def->predecessors[j]); + bb->predecessors->Insert(def->predecessors[j]); + } + if (def->type == kDalvikByteCode || def->type == kEntryBlock || def->type == kExitBlock) { + bb->data_flow_info = static_cast( + cu_.arena.Alloc(sizeof(BasicBlockDataFlow), kArenaAllocDFInfo)); + } + } + cu_.mir_graph->num_blocks_ = count; + ASSERT_EQ(count, cu_.mir_graph->block_list_.Size()); + cu_.mir_graph->entry_block_ = cu_.mir_graph->block_list_.Get(1); + ASSERT_EQ(kEntryBlock, cu_.mir_graph->entry_block_->block_type); + cu_.mir_graph->exit_block_ = cu_.mir_graph->block_list_.Get(2); + ASSERT_EQ(kExitBlock, cu_.mir_graph->exit_block_->block_type); + } + + template + void PrepareBasicBlocks(const BBDef (&defs)[count]) { + DoPrepareBasicBlocks(defs, count); + } + + void ComputeTopologicalSortOrder() { + cu_.mir_graph->SSATransformationStart(); + cu_.mir_graph->ComputeDFSOrders(); + cu_.mir_graph->ComputeDominators(); + cu_.mir_graph->ComputeTopologicalSortOrder(); + cu_.mir_graph->SSATransformationEnd(); + ASSERT_NE(cu_.mir_graph->topological_order_, nullptr); + ASSERT_NE(cu_.mir_graph->topological_order_loop_ends_, nullptr); + ASSERT_NE(cu_.mir_graph->topological_order_indexes_, nullptr); + ASSERT_EQ(cu_.mir_graph->GetNumBlocks(), cu_.mir_graph->topological_order_indexes_->Size()); + for (size_t i = 0, size = cu_.mir_graph->GetTopologicalSortOrder()->Size(); i != size; ++i) { + ASSERT_LT(cu_.mir_graph->topological_order_->Get(i), cu_.mir_graph->GetNumBlocks()); + BasicBlockId id = cu_.mir_graph->topological_order_->Get(i); + EXPECT_EQ(i, cu_.mir_graph->topological_order_indexes_->Get(id)); + } + } + + void DoCheckOrder(const BasicBlockId* ids, size_t count) { + ASSERT_EQ(count, cu_.mir_graph->GetTopologicalSortOrder()->Size()); + for (size_t i = 0; i != count; ++i) { + EXPECT_EQ(ids[i], cu_.mir_graph->GetTopologicalSortOrder()->Get(i)) << i; + } + } + + template + void CheckOrder(const BasicBlockId (&ids)[count]) { + DoCheckOrder(ids, count); + } + + void DoCheckLoopEnds(const uint16_t* ends, size_t count) { + for (size_t i = 0; i != count; ++i) { + ASSERT_LT(i, cu_.mir_graph->GetTopologicalSortOrderLoopEnds()->Size()); + EXPECT_EQ(ends[i], cu_.mir_graph->GetTopologicalSortOrderLoopEnds()->Get(i)) << i; + } + } + + template + void CheckLoopEnds(const uint16_t (&ends)[count]) { + DoCheckLoopEnds(ends, count); + } + + TopologicalSortOrderTest() + : pool_(), + cu_(&pool_) { + cu_.mir_graph.reset(new MIRGraph(&cu_, &cu_.arena)); + } + + ArenaPool pool_; + CompilationUnit cu_; +}; + +TEST_F(TopologicalSortOrderTest, DoWhile) { + const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(5)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(1)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(5, 4), DEF_PRED2(3, 4)), // "taken" loops to self. + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(4)), + }; + const BasicBlockId expected_order[] = { + 1, 3, 4, 5, 2 + }; + const uint16_t loop_ends[] = { + 0, 0, 3, 0, 0 + }; + + PrepareBasicBlocks(bbs); + ComputeTopologicalSortOrder(); + CheckOrder(expected_order); + CheckLoopEnds(loop_ends); +} + +TEST_F(TopologicalSortOrderTest, While) { + const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(5)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(4, 5), DEF_PRED2(1, 4)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(3), DEF_PRED1(3)), // Loops to 3. + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(3)), + }; + const BasicBlockId expected_order[] = { + 1, 3, 4, 5, 2 + }; + const uint16_t loop_ends[] = { + 0, 3, 0, 0, 0 + }; + + PrepareBasicBlocks(bbs); + ComputeTopologicalSortOrder(); + CheckOrder(expected_order); + CheckLoopEnds(loop_ends); +} + +TEST_F(TopologicalSortOrderTest, WhileWithTwoBackEdges) { + const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(6)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(4, 6), DEF_PRED3(1, 4, 5)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(5, 3), DEF_PRED1(3)), // Loops to 3. + DEF_BB(kDalvikByteCode, DEF_SUCC1(3), DEF_PRED1(4)), // Loops to 3. + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(3)), + }; + const BasicBlockId expected_order[] = { + 1, 3, 4, 5, 6, 2 + }; + const uint16_t loop_ends[] = { + 0, 4, 0, 0, 0, 0 + }; + + PrepareBasicBlocks(bbs); + ComputeTopologicalSortOrder(); + CheckOrder(expected_order); + CheckLoopEnds(loop_ends); +} + +TEST_F(TopologicalSortOrderTest, NestedLoop) { + const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(7)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(4, 7), DEF_PRED2(1, 6)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(5, 6), DEF_PRED2(3, 5)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(4)), // Loops to 4. + DEF_BB(kDalvikByteCode, DEF_SUCC1(3), DEF_PRED1(4)), // Loops to 3. + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(3)), + }; + const BasicBlockId expected_order[] = { + 1, 3, 4, 5, 6, 7, 2 + }; + const uint16_t loop_ends[] = { + 0, 5, 4, 0, 0, 0, 0 + }; + + PrepareBasicBlocks(bbs); + ComputeTopologicalSortOrder(); + CheckOrder(expected_order); + CheckLoopEnds(loop_ends); +} + +TEST_F(TopologicalSortOrderTest, NestedLoopHeadLoops) { + const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(6)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(4, 6), DEF_PRED2(1, 4)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(5, 3), DEF_PRED2(3, 5)), // Nested head, loops to 3. + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(4)), // Loops to 4. + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(3)), + }; + const BasicBlockId expected_order[] = { + 1, 3, 4, 5, 6, 2 + }; + const uint16_t loop_ends[] = { + 0, 4, 4, 0, 0, 0 + }; + + PrepareBasicBlocks(bbs); + ComputeTopologicalSortOrder(); + CheckOrder(expected_order); + CheckLoopEnds(loop_ends); +} + +TEST_F(TopologicalSortOrderTest, NestedLoopSameBackBranchBlock) { + const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(6)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(4, 6), DEF_PRED2(1, 5)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(5), DEF_PRED2(3, 5)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(4, 3), DEF_PRED1(4)), // Loops to 4 and 3. + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(3)), + }; + const BasicBlockId expected_order[] = { + 1, 3, 4, 5, 6, 2 + }; + const uint16_t loop_ends[] = { + 0, 4, 4, 0, 0, 0 + }; + + PrepareBasicBlocks(bbs); + ComputeTopologicalSortOrder(); + CheckOrder(expected_order); + CheckLoopEnds(loop_ends); +} + +TEST_F(TopologicalSortOrderTest, TwoReorderedInnerLoops) { + // This is a simplified version of real code graph where the branch from 8 to 5 must prevent + // the block 5 from being considered a loop head before processing the loop 7-8. + const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(9)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(4, 9), DEF_PRED2(1, 5)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(5, 7), DEF_PRED1(3)), // Branch over loop in 5. + DEF_BB(kDalvikByteCode, DEF_SUCC2(6, 3), DEF_PRED3(4, 6, 8)), // Loops to 4; inner loop. + DEF_BB(kDalvikByteCode, DEF_SUCC1(5), DEF_PRED1(5)), // Loops to 5. + DEF_BB(kDalvikByteCode, DEF_SUCC1(8), DEF_PRED2(4, 8)), // Loop head. + DEF_BB(kDalvikByteCode, DEF_SUCC2(7, 5), DEF_PRED1(7)), // Loops to 7; branches to 5. + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(3)), + }; + const BasicBlockId expected_order[] = { + 1, 3, 4, 7, 8, 5, 6, 9, 2 + }; + const uint16_t loop_ends[] = { + 0, 7, 0, 5, 0, 7, 0, 0, 0 + }; + + PrepareBasicBlocks(bbs); + ComputeTopologicalSortOrder(); + CheckOrder(expected_order); + CheckLoopEnds(loop_ends); +} + +TEST_F(TopologicalSortOrderTest, NestedLoopWithBackEdgeAfterOuterLoopBackEdge) { + // This is a simplified version of real code graph. The back-edge from 7 to the inner + // loop head 4 comes after the back-edge from 6 to the outer loop head 3. To make this + // appear a bit more complex, there's also a back-edge from 5 to 4. + const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(7)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED2(1, 6)), // Outer loop head. + DEF_BB(kDalvikByteCode, DEF_SUCC2(5, 6), DEF_PRED3(3, 5, 7)), // Inner loop head. + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(4)), // Loops to inner loop head 4. + DEF_BB(kDalvikByteCode, DEF_SUCC2(7, 3), DEF_PRED1(4)), // Loops to outer loop head 3. + DEF_BB(kDalvikByteCode, DEF_SUCC2(2, 4), DEF_PRED1(6)), // Loops to inner loop head 4. + }; + const BasicBlockId expected_order[] = { + // NOTE: The 5 goes before 6 only because 5 is a "fall-through" from 4 while 6 is "taken". + 1, 3, 4, 5, 6, 7, 2 + }; + const uint16_t loop_ends[] = { + 0, 6, 6, 0, 0, 0, 0 + }; + + PrepareBasicBlocks(bbs); + ComputeTopologicalSortOrder(); + CheckOrder(expected_order); + CheckLoopEnds(loop_ends); +} + +TEST_F(TopologicalSortOrderTest, LoopWithTwoEntryPoints) { + const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(7)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(5, 4), DEF_PRED1(1)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(5), DEF_PRED2(3, 6)), // Fall-back block is chosen as + DEF_BB(kDalvikByteCode, DEF_SUCC1(6), DEF_PRED2(3, 4)), // the earlier from these two. + DEF_BB(kDalvikByteCode, DEF_SUCC2(4, 7), DEF_PRED1(5)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(6)), + }; + const BasicBlockId expected_order[] = { + 1, 3, 4, 5, 6, 7, 2 + }; + const uint16_t loop_ends[] = { + 0, 0, 5, 0, 0, 0, 0 + }; + + PrepareBasicBlocks(bbs); + ComputeTopologicalSortOrder(); + CheckOrder(expected_order); + CheckLoopEnds(loop_ends); +} + +} // namespace art diff --git a/compiler/dex/mir_method_info.cc b/compiler/dex/mir_method_info.cc new file mode 100644 index 00000000000..cc2bd953d8c --- /dev/null +++ b/compiler/dex/mir_method_info.cc @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +# include "mir_method_info.h" + +#include "driver/compiler_driver.h" +#include "driver/dex_compilation_unit.h" +#include "driver/compiler_driver-inl.h" +#include "mirror/class_loader.h" // Only to allow casts in Handle. +#include "mirror/dex_cache.h" // Only to allow casts in Handle. +#include "scoped_thread_state_change.h" +#include "handle_scope-inl.h" + +namespace art { + +void MirMethodLoweringInfo::Resolve(CompilerDriver* compiler_driver, + const DexCompilationUnit* mUnit, + MirMethodLoweringInfo* method_infos, size_t count) { + if (kIsDebugBuild) { + DCHECK(method_infos != nullptr); + DCHECK_NE(count, 0u); + for (auto it = method_infos, end = method_infos + count; it != end; ++it) { + MirMethodLoweringInfo unresolved(it->MethodIndex(), it->GetInvokeType()); + if (it->target_dex_file_ != nullptr) { + unresolved.target_dex_file_ = it->target_dex_file_; + unresolved.target_method_idx_ = it->target_method_idx_; + } + DCHECK_EQ(memcmp(&unresolved, &*it, sizeof(*it)), 0); + } + } + + // We're going to resolve methods and check access in a tight loop. It's better to hold + // the lock and needed references once than re-acquiring them again and again. + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<3> hs(soa.Self()); + Handle dex_cache(hs.NewHandle(compiler_driver->GetDexCache(mUnit))); + Handle class_loader( + hs.NewHandle(compiler_driver->GetClassLoader(soa, mUnit))); + Handle referrer_class(hs.NewHandle( + compiler_driver->ResolveCompilingMethodsClass(soa, dex_cache, class_loader, mUnit))); + // Even if the referrer class is unresolved (i.e. we're compiling a method without class + // definition) we still want to resolve methods and record all available info. + + for (auto it = method_infos, end = method_infos + count; it != end; ++it) { + // Remember devirtualized invoke target and set the called method to the default. + MethodReference devirt_ref(it->target_dex_file_, it->target_method_idx_); + MethodReference* devirt_target = (it->target_dex_file_ != nullptr) ? &devirt_ref : nullptr; + it->target_dex_file_ = mUnit->GetDexFile(); + it->target_method_idx_ = it->MethodIndex(); + + InvokeType invoke_type = it->GetInvokeType(); + mirror::ArtMethod* resolved_method = + compiler_driver->ResolveMethod(soa, dex_cache, class_loader, mUnit, it->MethodIndex(), + invoke_type); + if (UNLIKELY(resolved_method == nullptr)) { + continue; + } + compiler_driver->GetResolvedMethodDexFileLocation(resolved_method, + &it->declaring_dex_file_, &it->declaring_class_idx_, &it->declaring_method_idx_); + it->vtable_idx_ = compiler_driver->GetResolvedMethodVTableIndex(resolved_method, invoke_type); + + MethodReference target_method(mUnit->GetDexFile(), it->MethodIndex()); + int fast_path_flags = compiler_driver->IsFastInvoke( + soa, dex_cache, class_loader, mUnit, referrer_class.Get(), resolved_method, &invoke_type, + &target_method, devirt_target, &it->direct_code_, &it->direct_method_); + bool needs_clinit = + compiler_driver->NeedsClassInitialization(referrer_class.Get(), resolved_method); + uint16_t other_flags = it->flags_ & + ~(kFlagFastPath | kFlagNeedsClassInitialization | (kInvokeTypeMask << kBitSharpTypeBegin)); + it->flags_ = other_flags | + (fast_path_flags != 0 ? kFlagFastPath : 0u) | + (static_cast(invoke_type) << kBitSharpTypeBegin) | + (needs_clinit ? kFlagNeedsClassInitialization : 0u); + it->target_dex_file_ = target_method.dex_file; + it->target_method_idx_ = target_method.dex_method_index; + it->stats_flags_ = fast_path_flags; + } +} + +} // namespace art diff --git a/compiler/dex/mir_method_info.h b/compiler/dex/mir_method_info.h new file mode 100644 index 00000000000..efe92f39431 --- /dev/null +++ b/compiler/dex/mir_method_info.h @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_DEX_MIR_METHOD_INFO_H_ +#define ART_COMPILER_DEX_MIR_METHOD_INFO_H_ + +#include "base/logging.h" +#include "base/macros.h" +#include "base/mutex.h" +#include "invoke_type.h" +#include "method_reference.h" + +namespace art { + +class CompilerDriver; +class DexCompilationUnit; +class DexFile; + +class MirMethodInfo { + public: + uint16_t MethodIndex() const { + return method_idx_; + } + + bool IsStatic() const { + return (flags_ & kFlagIsStatic) != 0u; + } + + bool IsResolved() const { + return declaring_dex_file_ != nullptr; + } + + const DexFile* DeclaringDexFile() const { + return declaring_dex_file_; + } + + uint16_t DeclaringClassIndex() const { + return declaring_class_idx_; + } + + uint16_t DeclaringMethodIndex() const { + return declaring_method_idx_; + } + + protected: + enum { + kBitIsStatic = 0, + kMethodInfoBitEnd + }; + COMPILE_ASSERT(kMethodInfoBitEnd <= 16, too_many_flags); + static constexpr uint16_t kFlagIsStatic = 1u << kBitIsStatic; + + MirMethodInfo(uint16_t method_idx, uint16_t flags) + : method_idx_(method_idx), + flags_(flags), + declaring_method_idx_(0u), + declaring_class_idx_(0u), + declaring_dex_file_(nullptr) { + } + + // Make copy-ctor/assign/dtor protected to avoid slicing. + MirMethodInfo(const MirMethodInfo& other) = default; + MirMethodInfo& operator=(const MirMethodInfo& other) = default; + ~MirMethodInfo() = default; + + // The method index in the compiling method's dex file. + uint16_t method_idx_; + // Flags, for volatility and derived class data. + uint16_t flags_; + // The method index in the dex file that defines the method, 0 if unresolved. + uint16_t declaring_method_idx_; + // The type index of the class declaring the method, 0 if unresolved. + uint16_t declaring_class_idx_; + // The dex file that defines the class containing the method and the method, + // nullptr if unresolved. + const DexFile* declaring_dex_file_; +}; + +class MirMethodLoweringInfo : public MirMethodInfo { + public: + // For each requested method retrieve the method's declaring location (dex file, class + // index and method index) and compute whether we can fast path the method call. For fast + // path methods, retrieve the method's vtable index and direct code and method when applicable. + static void Resolve(CompilerDriver* compiler_driver, const DexCompilationUnit* mUnit, + MirMethodLoweringInfo* method_infos, size_t count) + LOCKS_EXCLUDED(Locks::mutator_lock_); + + MirMethodLoweringInfo(uint16_t method_idx, InvokeType type) + : MirMethodInfo(method_idx, + ((type == kStatic) ? kFlagIsStatic : 0u) | + (static_cast(type) << kBitInvokeTypeBegin) | + (static_cast(type) << kBitSharpTypeBegin)), + direct_code_(0u), + direct_method_(0u), + target_dex_file_(nullptr), + target_method_idx_(0u), + vtable_idx_(0u), + stats_flags_(0) { + } + + void SetDevirtualizationTarget(const MethodReference& ref) { + DCHECK(target_dex_file_ == nullptr); + DCHECK_EQ(target_method_idx_, 0u); + DCHECK_LE(ref.dex_method_index, 0xffffu); + target_dex_file_ = ref.dex_file; + target_method_idx_ = ref.dex_method_index; + } + + bool FastPath() const { + return (flags_ & kFlagFastPath) != 0u; + } + + bool NeedsClassInitialization() const { + return (flags_ & kFlagNeedsClassInitialization) != 0u; + } + + InvokeType GetInvokeType() const { + return static_cast((flags_ >> kBitInvokeTypeBegin) & kInvokeTypeMask); + } + + art::InvokeType GetSharpType() const { + return static_cast((flags_ >> kBitSharpTypeBegin) & kInvokeTypeMask); + } + + MethodReference GetTargetMethod() const { + return MethodReference(target_dex_file_, target_method_idx_); + } + + uint16_t VTableIndex() const { + return vtable_idx_; + } + + uintptr_t DirectCode() const { + return direct_code_; + } + + uintptr_t DirectMethod() const { + return direct_method_; + } + + int StatsFlags() const { + return stats_flags_; + } + + private: + enum { + kBitFastPath = kMethodInfoBitEnd, + kBitInvokeTypeBegin, + kBitInvokeTypeEnd = kBitInvokeTypeBegin + 3, // 3 bits for invoke type. + kBitSharpTypeBegin, + kBitSharpTypeEnd = kBitSharpTypeBegin + 3, // 3 bits for sharp type. + kBitNeedsClassInitialization = kBitSharpTypeEnd, + kMethodLoweringInfoEnd + }; + COMPILE_ASSERT(kMethodLoweringInfoEnd <= 16, too_many_flags); + static constexpr uint16_t kFlagFastPath = 1u << kBitFastPath; + static constexpr uint16_t kFlagNeedsClassInitialization = 1u << kBitNeedsClassInitialization; + static constexpr uint16_t kInvokeTypeMask = 7u; + COMPILE_ASSERT((1u << (kBitInvokeTypeEnd - kBitInvokeTypeBegin)) - 1u == kInvokeTypeMask, + assert_invoke_type_bits_ok); + COMPILE_ASSERT((1u << (kBitSharpTypeEnd - kBitSharpTypeBegin)) - 1u == kInvokeTypeMask, + assert_sharp_type_bits_ok); + + uintptr_t direct_code_; + uintptr_t direct_method_; + // Before Resolve(), target_dex_file_ and target_method_idx_ hold the verification-based + // devirtualized invoke target if available, nullptr and 0u otherwise. + // After Resolve() they hold the actual target method that will be called; it will be either + // a devirtualized target method or the compilation's unit's dex file and MethodIndex(). + const DexFile* target_dex_file_; + uint16_t target_method_idx_; + uint16_t vtable_idx_; + int stats_flags_; + + friend class ClassInitCheckEliminationTest; +}; + +} // namespace art + +#endif // ART_COMPILER_DEX_MIR_METHOD_INFO_H_ diff --git a/compiler/dex/mir_optimization.cc b/compiler/dex/mir_optimization.cc index b7611f8f5bf..23ceb56d66e 100644 --- a/compiler/dex/mir_optimization.cc +++ b/compiler/dex/mir_optimization.cc @@ -15,8 +15,13 @@ */ #include "compiler_internals.h" +#include "global_value_numbering.h" #include "local_value_numbering.h" #include "dataflow_iterator-inl.h" +#include "dex/global_value_numbering.h" +#include "dex/quick/dex_file_method_inliner.h" +#include "dex/quick/dex_file_to_method_inliner_map.h" +#include "utils/scoped_arena_containers.h" namespace art { @@ -32,17 +37,23 @@ void MIRGraph::SetConstant(int32_t ssa_reg, int value) { void MIRGraph::SetConstantWide(int ssa_reg, int64_t value) { is_constant_v_->SetBit(ssa_reg); + is_constant_v_->SetBit(ssa_reg + 1); constant_values_[ssa_reg] = Low32Bits(value); constant_values_[ssa_reg + 1] = High32Bits(value); } -void MIRGraph::DoConstantPropogation(BasicBlock* bb) { +void MIRGraph::DoConstantPropagation(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]; + // Skip pass if BB has MIR without SSA representation. + if (mir->ssa_rep == nullptr) { + return; + } + + uint64_t df_attributes = GetDataFlowAttributes(mir); - DecodedInstruction *d_insn = &mir->dalvikInsn; + MIR::DecodedInstruction* d_insn = &mir->dalvikInsn; if (!(df_attributes & DF_HAS_DEFS)) continue; @@ -92,23 +103,13 @@ void MIRGraph::DoConstantPropogation(BasicBlock* bb) { /* TODO: implement code to handle arithmetic operations */ } -void MIRGraph::PropagateConstants() { - is_constant_v_ = new (arena_) ArenaBitVector(arena_, GetNumSSARegs(), false); - constant_values_ = static_cast(arena_->Alloc(sizeof(int) * GetNumSSARegs(), - 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) { +MIR* MIRGraph::AdvanceMIR(BasicBlock** p_bb, MIR* mir) { BasicBlock* bb = *p_bb; if (mir != NULL) { mir = mir->next; if (mir == NULL) { - bb = bb->fall_through; + bb = GetBasicBlock(bb->fall_through); if ((bb == NULL) || Predecessors(bb) != 1) { mir = NULL; } else { @@ -131,35 +132,36 @@ 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 { + if (MIR::DecodedInstruction::IsPseudoMirOp(mir->dalvikInsn.opcode)) { mir = AdvanceMIR(&tbb, mir); + } else { + mir = NULL; } } return mir; } -static BasicBlock* NextDominatedBlock(BasicBlock* bb) { +BasicBlock* MIRGraph::NextDominatedBlock(BasicBlock* bb) { if (bb->block_type == kDead) { return NULL; } DCHECK((bb->block_type == kEntryBlock) || (bb->block_type == kDalvikByteCode) || (bb->block_type == kExitBlock)); - if (((bb->taken != NULL) && (bb->fall_through == NULL)) && - ((bb->taken->block_type == kDalvikByteCode) || (bb->taken->block_type == kExitBlock))) { + BasicBlock* bb_taken = GetBasicBlock(bb->taken); + BasicBlock* bb_fall_through = GetBasicBlock(bb->fall_through); + if (((bb_fall_through == NULL) && (bb_taken != NULL)) && + ((bb_taken->block_type == kDalvikByteCode) || (bb_taken->block_type == kExitBlock))) { // Follow simple unconditional branches. - bb = bb->taken; + bb = bb_taken; } else { // Follow simple fallthrough - bb = (bb->taken != NULL) ? NULL : bb->fall_through; + bb = (bb_taken != NULL) ? NULL : bb_fall_through; } if (bb == NULL || (Predecessors(bb) != 1)) { return NULL; @@ -203,22 +205,139 @@ static SelectInstructionKind SelectKind(MIR* mir) { } } +static constexpr ConditionCode kIfCcZConditionCodes[] = { + kCondEq, kCondNe, kCondLt, kCondGe, kCondGt, kCondLe +}; + +COMPILE_ASSERT(arraysize(kIfCcZConditionCodes) == Instruction::IF_LEZ - Instruction::IF_EQZ + 1, + if_ccz_ccodes_size1); + +static constexpr bool IsInstructionIfCcZ(Instruction::Code opcode) { + return Instruction::IF_EQZ <= opcode && opcode <= Instruction::IF_LEZ; +} + +static constexpr ConditionCode ConditionCodeForIfCcZ(Instruction::Code opcode) { + return kIfCcZConditionCodes[opcode - Instruction::IF_EQZ]; +} + +COMPILE_ASSERT(ConditionCodeForIfCcZ(Instruction::IF_EQZ) == kCondEq, check_if_eqz_ccode); +COMPILE_ASSERT(ConditionCodeForIfCcZ(Instruction::IF_NEZ) == kCondNe, check_if_nez_ccode); +COMPILE_ASSERT(ConditionCodeForIfCcZ(Instruction::IF_LTZ) == kCondLt, check_if_ltz_ccode); +COMPILE_ASSERT(ConditionCodeForIfCcZ(Instruction::IF_GEZ) == kCondGe, check_if_gez_ccode); +COMPILE_ASSERT(ConditionCodeForIfCcZ(Instruction::IF_GTZ) == kCondGt, check_if_gtz_ccode); +COMPILE_ASSERT(ConditionCodeForIfCcZ(Instruction::IF_LEZ) == kCondLe, check_if_lez_ccode); + int MIRGraph::GetSSAUseCount(int s_reg) { return raw_use_counts_.Get(s_reg); } +size_t MIRGraph::GetNumAvailableNonSpecialCompilerTemps() { + if (num_non_special_compiler_temps_ >= max_available_non_special_compiler_temps_) { + return 0; + } else { + return max_available_non_special_compiler_temps_ - num_non_special_compiler_temps_; + } +} + + +// FIXME - will probably need to revisit all uses of this, as type not defined. +static const RegLocation temp_loc = {kLocCompilerTemp, + 0, 1 /*defined*/, 0, 0, 0, 0, 0, 1 /*home*/, + RegStorage(), INVALID_SREG, INVALID_SREG}; + +CompilerTemp* MIRGraph::GetNewCompilerTemp(CompilerTempType ct_type, bool wide) { + // There is a limit to the number of non-special temps so check to make sure it wasn't exceeded. + if (ct_type == kCompilerTempVR) { + size_t available_temps = GetNumAvailableNonSpecialCompilerTemps(); + if (available_temps <= 0 || (available_temps <= 1 && wide)) { + return 0; + } + } + + CompilerTemp *compiler_temp = static_cast(arena_->Alloc(sizeof(CompilerTemp), + kArenaAllocRegAlloc)); + + // Create the type of temp requested. Special temps need special handling because + // they have a specific virtual register assignment. + if (ct_type == kCompilerTempSpecialMethodPtr) { + DCHECK_EQ(wide, false); + compiler_temp->v_reg = static_cast(kVRegMethodPtrBaseReg); + compiler_temp->s_reg_low = AddNewSReg(compiler_temp->v_reg); + + // The MIR graph keeps track of the sreg for method pointer specially, so record that now. + method_sreg_ = compiler_temp->s_reg_low; + } else { + DCHECK_EQ(ct_type, kCompilerTempVR); + + // The new non-special compiler temp must receive a unique v_reg with a negative value. + compiler_temp->v_reg = static_cast(kVRegNonSpecialTempBaseReg) - + num_non_special_compiler_temps_; + compiler_temp->s_reg_low = AddNewSReg(compiler_temp->v_reg); + num_non_special_compiler_temps_++; + + if (wide) { + // Create a new CompilerTemp for the high part. + CompilerTemp *compiler_temp_high = + static_cast(arena_->Alloc(sizeof(CompilerTemp), kArenaAllocRegAlloc)); + compiler_temp_high->v_reg = compiler_temp->v_reg; + compiler_temp_high->s_reg_low = compiler_temp->s_reg_low; + compiler_temps_.Insert(compiler_temp_high); + + // Ensure that the two registers are consecutive. Since the virtual registers used for temps + // grow in a negative fashion, we need the smaller to refer to the low part. Thus, we + // redefine the v_reg and s_reg_low. + compiler_temp->v_reg--; + int ssa_reg_high = compiler_temp->s_reg_low; + compiler_temp->s_reg_low = AddNewSReg(compiler_temp->v_reg); + int ssa_reg_low = compiler_temp->s_reg_low; + + // If needed initialize the register location for the high part. + // The low part is handled later in this method on a common path. + if (reg_location_ != nullptr) { + reg_location_[ssa_reg_high] = temp_loc; + reg_location_[ssa_reg_high].high_word = 1; + reg_location_[ssa_reg_high].s_reg_low = ssa_reg_low; + reg_location_[ssa_reg_high].wide = true; + } + + num_non_special_compiler_temps_++; + } + } + + // Have we already allocated the register locations? + if (reg_location_ != nullptr) { + int ssa_reg_low = compiler_temp->s_reg_low; + reg_location_[ssa_reg_low] = temp_loc; + reg_location_[ssa_reg_low].s_reg_low = ssa_reg_low; + reg_location_[ssa_reg_low].wide = wide; + } + + compiler_temps_.Insert(compiler_temp); + return compiler_temp; +} /* 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_); + // Don't do a separate LVN if we did the GVN. + bool use_lvn = bb->use_lvn && (cu_->disable_opt & (1u << kGlobalValueNumbering)) != 0u; + std::unique_ptr allocator; + std::unique_ptr global_valnum; + std::unique_ptr local_valnum; + if (use_lvn) { + allocator.reset(ScopedArenaAllocator::Create(&cu_->arena_stack)); + global_valnum.reset(new (allocator.get()) GlobalValueNumbering(cu_, allocator.get())); + local_valnum.reset(new (allocator.get()) LocalValueNumbering(global_valnum.get(), bb->id, + allocator.get())); + } 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); + if (use_lvn) { + local_valnum->GetValueNumber(mir); + } // Look for interesting opcodes, skip otherwise Instruction::Code opcode = mir->dalvikInsn.opcode; switch (opcode) { @@ -233,35 +352,11 @@ bool MIRGraph::BasicBlockOpt(BasicBlock* bb) { } 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) && + if (IsInstructionIfCcZ(mir_next->dalvikInsn.opcode) && (mir->ssa_rep->defs[0] == mir_next->ssa_rep->uses[0]) && (GetSSAUseCount(mir->ssa_rep->defs[0]) == 1)) { - mir_next->dalvikInsn.arg[0] = ccode; + mir_next->meta.ccode = ConditionCodeForIfCcZ(mir_next->dalvikInsn.opcode); switch (opcode) { case Instruction::CMPL_FLOAT: mir_next->dalvikInsn.opcode = @@ -286,12 +381,16 @@ bool MIRGraph::BasicBlockOpt(BasicBlock* bb) { default: LOG(ERROR) << "Unexpected opcode: " << opcode; } mir->dalvikInsn.opcode = static_cast(kMirOpNop); + // Copy the SSA information that is relevant. 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; + // Copy in the decoded instruction information for potential SSA re-creation. + mir_next->dalvikInsn.vA = mir->dalvikInsn.vB; + mir_next->dalvikInsn.vB = mir->dalvikInsn.vC; } } break; @@ -311,11 +410,13 @@ bool MIRGraph::BasicBlockOpt(BasicBlock* bb) { case Instruction::IF_GTZ: case Instruction::IF_LEZ: // If we've got a backwards branch to return, no need to suspend check. - if ((IsBackedge(bb, bb->taken) && bb->taken->dominates_return) || - (IsBackedge(bb, bb->fall_through) && bb->fall_through->dominates_return)) { + if ((IsBackedge(bb, bb->taken) && GetBasicBlock(bb->taken)->dominates_return) || + (IsBackedge(bb, bb->fall_through) && + GetBasicBlock(bb->fall_through)->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; + LOG(INFO) << "Suppressed suspend check on branch to return at 0x" << std::hex + << mir->offset; } } break; @@ -323,20 +424,21 @@ bool MIRGraph::BasicBlockOpt(BasicBlock* bb) { break; } // Is this the select pattern? - // TODO: flesh out support for Mips and X86. NOTE: llvm's select op doesn't quite work here. + // TODO: flesh out support for Mips. 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; + if (!cu_->compiler->IsPortable() && + (cu_->instruction_set == kArm64 || cu_->instruction_set == kThumb2 || + cu_->instruction_set == kX86 || cu_->instruction_set == kX86_64) && + IsInstructionIfCcZ(mir->dalvikInsn.opcode)) { + BasicBlock* ft = GetBasicBlock(bb->fall_through); DCHECK(ft != NULL); - BasicBlock* ft_ft = ft->fall_through; - BasicBlock* ft_tk = ft->taken; + BasicBlock* ft_ft = GetBasicBlock(ft->fall_through); + BasicBlock* ft_tk = GetBasicBlock(ft->taken); - BasicBlock* tk = bb->taken; + BasicBlock* tk = GetBasicBlock(bb->taken); DCHECK(tk != NULL); - BasicBlock* tk_ft = tk->fall_through; - BasicBlock* tk_tk = tk->taken; + BasicBlock* tk_ft = GetBasicBlock(tk->fall_through); + BasicBlock* tk_tk = GetBasicBlock(tk->taken); /* * In the select pattern, the taken edge goes to a block that unconditionally @@ -352,6 +454,8 @@ bool MIRGraph::BasicBlockOpt(BasicBlock* bb) { if (SelectKind(tk->last_mir_insn) == kSelectGoto) { tk->last_mir_insn->optimization_flags |= (MIR_IGNORE_SUSPEND_CHECK); } + + // TODO: Add logic for LONG. // 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) && @@ -374,12 +478,7 @@ bool MIRGraph::BasicBlockOpt(BasicBlock* bb) { * 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->meta.ccode = ConditionCodeForIfCcZ(mir->dalvikInsn.opcode); mir->dalvikInsn.opcode = static_cast(kMirOpSelect); bool const_form = (SelectKind(if_true) == kSelectConst); if ((SelectKind(if_true) == kSelectMove)) { @@ -391,6 +490,11 @@ bool MIRGraph::BasicBlockOpt(BasicBlock* bb) { } } if (const_form) { + /* + * TODO: If both constants are the same value, then instead of generating + * a select, we should simply generate a const bytecode. This should be + * considered after inlining which can lead to CFG of this form. + */ // "true" set val in vB mir->dalvikInsn.vB = if_true->dalvikInsn.vB; // "false" set val in vC @@ -399,7 +503,7 @@ bool MIRGraph::BasicBlockOpt(BasicBlock* bb) { DCHECK_EQ(SelectKind(if_true), kSelectMove); DCHECK_EQ(SelectKind(if_false), kSelectMove); int* src_ssa = - static_cast(arena_->Alloc(sizeof(int) * 3, ArenaAllocator::kAllocDFInfo)); + static_cast(arena_->Alloc(sizeof(int) * 3, kArenaAllocDFInfo)); 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]; @@ -408,14 +512,14 @@ bool MIRGraph::BasicBlockOpt(BasicBlock* bb) { } mir->ssa_rep->num_defs = 1; mir->ssa_rep->defs = - static_cast(arena_->Alloc(sizeof(int) * 1, ArenaAllocator::kAllocDFInfo)); + static_cast(arena_->Alloc(sizeof(int) * 1, kArenaAllocDFInfo)); mir->ssa_rep->fp_def = - static_cast(arena_->Alloc(sizeof(bool) * 1, ArenaAllocator::kAllocDFInfo)); + static_cast(arena_->Alloc(sizeof(bool) * 1, kArenaAllocDFInfo)); mir->ssa_rep->fp_def[0] = if_true->ssa_rep->fp_def[0]; // Match type of uses to def. mir->ssa_rep->fp_use = static_cast(arena_->Alloc(sizeof(bool) * mir->ssa_rep->num_uses, - ArenaAllocator::kAllocDFInfo)); + kArenaAllocDFInfo)); for (int i = 0; i < mir->ssa_rep->num_uses; i++) { mir->ssa_rep->fp_use[i] = mir->ssa_rep->fp_def[0]; } @@ -434,7 +538,7 @@ bool MIRGraph::BasicBlockOpt(BasicBlock* bb) { 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); + BasicBlockId* incoming = phi->meta.phi_incoming; for (int i = 0; i < phi->ssa_rep->num_uses; i++) { if (phi->ssa_rep->uses[i] == live_def) { incoming[i] = bb->id; @@ -449,7 +553,7 @@ bool MIRGraph::BasicBlockOpt(BasicBlock* bb) { } } phi->ssa_rep->num_uses--; - bb->taken = NULL; + bb->taken = NullBasicBlockId; tk->block_type = kDead; for (MIR* tmir = ft->first_mir_insn; tmir != NULL; tmir = tmir->next) { tmir->dalvikInsn.opcode = static_cast(kMirOpNop); @@ -459,20 +563,13 @@ bool MIRGraph::BasicBlockOpt(BasicBlock* bb) { } } } - bb = NextDominatedBlock(bb); + bb = ((cu_->disable_opt & (1 << kSuppressExceptionEdges)) != 0) ? NextDominatedBlock(bb) : NULL; } - - if (num_temps > cu_->num_compiler_temps) { - cu_->num_compiler_temps = num_temps; + if (use_lvn && UNLIKELY(!global_valnum->Good())) { + LOG(WARNING) << "LVN overflow in " << PrettyMethod(cu_->method_idx, *cu_->dex_file); } - 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); - } + return true; } /* Collect stats on number of checks removed */ @@ -482,7 +579,7 @@ void MIRGraph::CountChecks(struct BasicBlock* bb) { if (mir->ssa_rep == NULL) { continue; } - int df_attributes = oat_data_flow_attributes_[mir->dalvikInsn.opcode]; + uint64_t df_attributes = GetDataFlowAttributes(mir); if (df_attributes & DF_HAS_NULL_CHKS) { checkstats_->null_checks++; if (mir->optimization_flags & MIR_IGNORE_NULL_CHECK) { @@ -500,7 +597,7 @@ void MIRGraph::CountChecks(struct BasicBlock* bb) { } /* Try to make common case the fallthrough path */ -static bool LayoutBlocks(struct BasicBlock* bb) { +bool MIRGraph::LayoutBlocks(BasicBlock* bb) { // TODO: For now, just looking for direct throws. Consider generalizing for profile feedback if (!bb->explicit_throw) { return false; @@ -511,13 +608,13 @@ static bool LayoutBlocks(struct BasicBlock* bb) { if ((walker->block_type == kEntryBlock) || (Predecessors(walker) != 1)) { break; } - BasicBlock* prev = walker->predecessors->Get(0); + BasicBlock* prev = GetBasicBlock(walker->predecessors->Get(0)); if (prev->conditional_branch) { - if (prev->fall_through == walker) { + if (GetBasicBlock(prev->fall_through) == walker) { // Already done - return break; } - DCHECK_EQ(walker, prev->taken); + DCHECK_EQ(walker, GetBasicBlock(prev->taken)); // Got one. Flip it and exit Instruction::Code opcode = prev->last_mir_insn->dalvikInsn.opcode; switch (opcode) { @@ -536,7 +633,7 @@ static bool LayoutBlocks(struct BasicBlock* bb) { default: LOG(FATAL) << "Unexpected opcode " << opcode; } prev->last_mir_insn->dalvikInsn.opcode = opcode; - BasicBlock* t_bb = prev->taken; + BasicBlockId t_bb = prev->taken; prev->taken = prev->fall_through; prev->fall_through = t_bb; break; @@ -547,7 +644,7 @@ static bool LayoutBlocks(struct BasicBlock* bb) { } /* Combine any basic blocks terminated by instructions that we now know can't throw */ -bool MIRGraph::CombineBlocks(struct BasicBlock* bb) { +void MIRGraph::CombineBlocks(struct BasicBlock* bb) { // Loop here to allow combining a sequence of blocks while (true) { // Check termination conditions @@ -556,8 +653,9 @@ bool MIRGraph::CombineBlocks(struct BasicBlock* bb) { || (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) + || (bb->taken == NullBasicBlockId) + || (GetBasicBlock(bb->taken)->block_type != kExceptionHandling) + || (bb->successor_block_list_type != kNotUsed) || (static_cast(bb->last_mir_insn->dalvikInsn.opcode) != kMirOpCheck)) { break; } @@ -566,7 +664,7 @@ bool MIRGraph::CombineBlocks(struct BasicBlock* bb) { 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]; + uint64_t df_attributes = GetDataFlowAttributes(throw_insn); bool can_combine = true; if (df_attributes & DF_HAS_NULL_CHKS) { can_combine &= ((throw_insn->optimization_flags & MIR_IGNORE_NULL_CHECK) != 0); @@ -578,19 +676,18 @@ bool MIRGraph::CombineBlocks(struct BasicBlock* bb) { break; } // OK - got one. Combine - BasicBlock* bb_next = bb->fall_through; + BasicBlock* bb_next = GetBasicBlock(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; + bb->successor_block_list_type = bb_next->successor_block_list_type; + bb->successor_blocks = bb_next->successor_blocks; // 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 + GetBasicBlock(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; @@ -611,61 +708,100 @@ bool MIRGraph::CombineBlocks(struct BasicBlock* bb) { // 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) { +void MIRGraph::EliminateNullChecksAndInferTypesStart() { + if ((cu_->disable_opt & (1 << kNullCheckElimination)) == 0) { + if (kIsDebugBuild) { + AllNodesIterator iter(this); + for (BasicBlock* bb = iter.Next(); bb != nullptr; bb = iter.Next()) { + CHECK(bb->data_flow_info == nullptr || bb->data_flow_info->ending_check_v == nullptr); + } + } + + DCHECK(temp_scoped_alloc_.get() == nullptr); + temp_scoped_alloc_.reset(ScopedArenaAllocator::Create(&cu_->arena_stack)); + temp_bit_vector_size_ = GetNumSSARegs(); + temp_bit_vector_ = new (temp_scoped_alloc_.get()) ArenaBitVector( + temp_scoped_alloc_.get(), temp_bit_vector_size_, false, kBitMapTempSSARegisterV); + } +} + +/* + * Eliminate unnecessary null checks for a basic block. Also, while we're doing + * an iterative walk go ahead and perform type and size inference. + */ +bool MIRGraph::EliminateNullChecksAndInferTypes(BasicBlock* bb) { if (bb->data_flow_info == NULL) return false; + bool infer_changed = false; + bool do_nce = ((cu_->disable_opt & (1 << kNullCheckElimination)) == 0); - /* - * 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 if (bb->predecessors->Size() == 1) { - BasicBlock* pred_bb = bb->predecessors->Get(0); - temp_ssa_register_v_->Copy(pred_bb->data_flow_info->ending_null_check_v); - if (pred_bb->block_type == kDalvikByteCode) { - // Check to see if predecessor had an explicit null-check. - MIR* last_insn = pred_bb->last_mir_insn; - Instruction::Code last_opcode = last_insn->dalvikInsn.opcode; - if (last_opcode == Instruction::IF_EQZ) { - if (pred_bb->fall_through == bb) { - // The fall-through of a block following a IF_EQZ, set the vA of the IF_EQZ to show that - // it can't be null. - temp_ssa_register_v_->SetBit(last_insn->ssa_rep->uses[0]); - } - } else if (last_opcode == Instruction::IF_NEZ) { - if (pred_bb->taken == bb) { - // The taken block following a IF_NEZ, set the vA of the IF_NEZ to show that it can't be - // null. - temp_ssa_register_v_->SetBit(last_insn->ssa_rep->uses[0]); + ArenaBitVector* ssa_regs_to_check = temp_bit_vector_; + if (do_nce) { + /* + * Set initial state. Catch blocks don't need any special treatment. + */ + if (bb->block_type == kEntryBlock) { + ssa_regs_to_check->ClearAllBits(); + // Assume all ins are objects. + for (uint16_t in_reg = cu_->num_dalvik_registers - cu_->num_ins; + in_reg < cu_->num_dalvik_registers; in_reg++) { + ssa_regs_to_check->SetBit(in_reg); + } + 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; + ssa_regs_to_check->ClearBit(this_reg); + } + } else if (bb->predecessors->Size() == 1) { + BasicBlock* pred_bb = GetBasicBlock(bb->predecessors->Get(0)); + // pred_bb must have already been processed at least once. + DCHECK(pred_bb->data_flow_info->ending_check_v != nullptr); + ssa_regs_to_check->Copy(pred_bb->data_flow_info->ending_check_v); + if (pred_bb->block_type == kDalvikByteCode) { + // Check to see if predecessor had an explicit null-check. + MIR* last_insn = pred_bb->last_mir_insn; + if (last_insn != nullptr) { + Instruction::Code last_opcode = last_insn->dalvikInsn.opcode; + if (last_opcode == Instruction::IF_EQZ) { + if (pred_bb->fall_through == bb->id) { + // The fall-through of a block following a IF_EQZ, set the vA of the IF_EQZ to show that + // it can't be null. + ssa_regs_to_check->ClearBit(last_insn->ssa_rep->uses[0]); + } + } else if (last_opcode == Instruction::IF_NEZ) { + if (pred_bb->taken == bb->id) { + // The taken block following a IF_NEZ, set the vA of the IF_NEZ to show that it can't be + // null. + ssa_regs_to_check->ClearBit(last_insn->ssa_rep->uses[0]); + } + } } } - } - } else { - // Starting state is intersection 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; + } else { + // Starting state is union of all incoming arcs + GrowableArray::Iterator iter(bb->predecessors); + BasicBlock* pred_bb = GetBasicBlock(iter.Next()); + CHECK(pred_bb != NULL); + while (pred_bb->data_flow_info->ending_check_v == nullptr) { + pred_bb = GetBasicBlock(iter.Next()); + // At least one predecessor must have been processed before this bb. + DCHECK(pred_bb != nullptr); + DCHECK(pred_bb->data_flow_info != nullptr); + } + ssa_regs_to_check->Copy(pred_bb->data_flow_info->ending_check_v); + while (true) { + pred_bb = GetBasicBlock(iter.Next()); + if (!pred_bb) break; + DCHECK(pred_bb->data_flow_info != nullptr); + if (pred_bb->data_flow_info->ending_check_v == nullptr) { + continue; + } + ssa_regs_to_check->Union(pred_bb->data_flow_info->ending_check_v); } - temp_ssa_register_v_->Intersect(pred_bb->data_flow_info->ending_null_check_v); } + // At this point, ssa_regs_to_check shows which sregs have an object definition with + // no intervening uses. } // Walk through the instruction in the block, updating as necessary @@ -673,11 +809,56 @@ bool MIRGraph::EliminateNullChecks(struct BasicBlock* bb) { if (mir->ssa_rep == NULL) { continue; } - int df_attributes = oat_data_flow_attributes_[mir->dalvikInsn.opcode]; - // Mark target of NEW* as non-null + // Propagate type info. + infer_changed = InferTypeAndSize(bb, mir, infer_changed); + if (!do_nce) { + continue; + } + + uint64_t df_attributes = GetDataFlowAttributes(mir); + + // Might need a null check? + if (df_attributes & DF_HAS_NULL_CHKS) { + 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 (!ssa_regs_to_check->IsBitSet(src_sreg)) { + // Eliminate the null check. + mir->optimization_flags |= MIR_IGNORE_NULL_CHECK; + } else { + // Do the null check. + mir->optimization_flags &= ~MIR_IGNORE_NULL_CHECK; + // Mark s_reg as null-checked + ssa_regs_to_check->ClearBit(src_sreg); + } + } + + if ((df_attributes & DF_A_WIDE) || + (df_attributes & (DF_REF_A | DF_SETS_CONST | DF_NULL_TRANSFER)) == 0) { + continue; + } + + /* + * First, mark all object definitions as requiring null check. + * Note: we can't tell if a CONST definition might be used as an object, so treat + * them all as object definitions. + */ + if (((df_attributes & (DF_DA | DF_REF_A)) == (DF_DA | DF_REF_A)) || + (df_attributes & DF_SETS_CONST)) { + ssa_regs_to_check->SetBit(mir->ssa_rep->defs[0]); + } + + // Now, remove mark from all object definitions we know are non-null. if (df_attributes & DF_NON_NULL_DST) { - temp_ssa_register_v_->SetBit(mir->ssa_rep->defs[0]); + // Mark target of NEW* as non-null + ssa_regs_to_check->ClearBit(mir->ssa_rep->defs[0]); } // Mark non-null returns from invoke-style NEW* @@ -687,22 +868,22 @@ bool MIRGraph::EliminateNullChecks(struct BasicBlock* bb) { 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]); + ssa_regs_to_check->ClearBit(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) { + } else if (bb->fall_through != NullBasicBlockId) { // Look in next basic block - struct BasicBlock* next_bb = bb->fall_through; + struct BasicBlock* next_bb = GetBasicBlock(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)) { + if (MIR::DecodedInstruction::IsPseudoMirOp(tmir->dalvikInsn.opcode)) { 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]); + ssa_regs_to_check->ClearBit(tmir->ssa_rep->defs[0]); } else { LOG(WARNING) << "Unexpected op after new: " << tmir->dalvikInsn.opcode; } @@ -715,96 +896,400 @@ bool MIRGraph::EliminateNullChecks(struct BasicBlock* bb) { /* * Propagate nullcheck state on register copies (including * Phi pseudo copies. For the latter, nullcheck state is - * the "and" of all the Phi's operands. + * the "or" 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; + bool needs_null_check = false; for (int i = 0; i < operands; i++) { - null_checked &= temp_ssa_register_v_->IsBitSet(mir->ssa_rep->uses[i]); + needs_null_check |= ssa_regs_to_check->IsBitSet(mir->ssa_rep->uses[i]); } - if (null_checked) { - temp_ssa_register_v_->SetBit(tgt_sreg); + if (needs_null_check) { + ssa_regs_to_check->SetBit(tgt_sreg); + } else { + ssa_regs_to_check->ClearBit(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; + // Did anything change? + bool nce_changed = false; + if (do_nce) { + if (bb->data_flow_info->ending_check_v == nullptr) { + DCHECK(temp_scoped_alloc_.get() != nullptr); + bb->data_flow_info->ending_check_v = new (temp_scoped_alloc_.get()) ArenaBitVector( + temp_scoped_alloc_.get(), temp_bit_vector_size_, false, kBitMapNullCheck); + nce_changed = ssa_regs_to_check->GetHighestBitSet() != -1; + bb->data_flow_info->ending_check_v->Copy(ssa_regs_to_check); + } else if (!ssa_regs_to_check->SameBitsSet(bb->data_flow_info->ending_check_v)) { + nce_changed = true; + bb->data_flow_info->ending_check_v->Copy(ssa_regs_to_check); + } + } + return infer_changed | nce_changed; +} + +void MIRGraph::EliminateNullChecksAndInferTypesEnd() { + if ((cu_->disable_opt & (1 << kNullCheckElimination)) == 0) { + // Clean up temporaries. + temp_bit_vector_size_ = 0u; + temp_bit_vector_ = nullptr; + AllNodesIterator iter(this); + for (BasicBlock* bb = iter.Next(); bb != nullptr; bb = iter.Next()) { + if (bb->data_flow_info != nullptr) { + bb->data_flow_info->ending_check_v = nullptr; } - 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); + } + DCHECK(temp_scoped_alloc_.get() != nullptr); + temp_scoped_alloc_.reset(); + } +} + +bool MIRGraph::EliminateClassInitChecksGate() { + if ((cu_->disable_opt & (1 << kClassInitCheckElimination)) != 0 || + !cu_->mir_graph->HasStaticFieldAccess()) { + return false; + } + + if (kIsDebugBuild) { + AllNodesIterator iter(this); + for (BasicBlock* bb = iter.Next(); bb != nullptr; bb = iter.Next()) { + CHECK(bb->data_flow_info == nullptr || bb->data_flow_info->ending_check_v == nullptr); + } + } + + DCHECK(temp_scoped_alloc_.get() == nullptr); + temp_scoped_alloc_.reset(ScopedArenaAllocator::Create(&cu_->arena_stack)); + + // Each insn we use here has at least 2 code units, offset/2 will be a unique index. + const size_t end = (cu_->code_item->insns_size_in_code_units_ + 1u) / 2u; + temp_insn_data_ = static_cast( + temp_scoped_alloc_->Alloc(end * sizeof(*temp_insn_data_), kArenaAllocGrowableArray)); + + uint32_t unique_class_count = 0u; + { + // Get unique_class_count and store indexes in temp_insn_data_ using a map on a nested + // ScopedArenaAllocator. + + // Embed the map value in the entry to save space. + struct MapEntry { + // Map key: the class identified by the declaring dex file and type index. + const DexFile* declaring_dex_file; + uint16_t declaring_class_idx; + // Map value: index into bit vectors of classes requiring initialization checks. + uint16_t index; + }; + struct MapEntryComparator { + bool operator()(const MapEntry& lhs, const MapEntry& rhs) const { + if (lhs.declaring_class_idx != rhs.declaring_class_idx) { + return lhs.declaring_class_idx < rhs.declaring_class_idx; } - } + return lhs.declaring_dex_file < rhs.declaring_dex_file; + } + }; + + ScopedArenaAllocator allocator(&cu_->arena_stack); + ScopedArenaSet class_to_index_map(MapEntryComparator(), + allocator.Adapter()); + + // First, find all SGET/SPUTs that may need class initialization checks, record INVOKE_STATICs. + AllNodesIterator iter(this); + for (BasicBlock* bb = iter.Next(); bb != nullptr; bb = iter.Next()) { + for (MIR* mir = bb->first_mir_insn; mir != nullptr; mir = mir->next) { + DCHECK(bb->data_flow_info != nullptr); + if (mir->dalvikInsn.opcode >= Instruction::SGET && + mir->dalvikInsn.opcode <= Instruction::SPUT_SHORT) { + const MirSFieldLoweringInfo& field_info = GetSFieldLoweringInfo(mir); + uint16_t index = 0xffffu; + if (!field_info.IsInitialized()) { + DCHECK_LT(class_to_index_map.size(), 0xffffu); + MapEntry entry = { + // Treat unresolved fields as if each had its own class. + field_info.IsResolved() ? field_info.DeclaringDexFile() + : nullptr, + field_info.IsResolved() ? field_info.DeclaringClassIndex() + : field_info.FieldIndex(), + static_cast(class_to_index_map.size()) + }; + index = class_to_index_map.insert(entry).first->index; + } + // Using offset/2 for index into temp_insn_data_. + temp_insn_data_[mir->offset / 2u] = index; + } + } + } + unique_class_count = static_cast(class_to_index_map.size()); + } + + if (unique_class_count == 0u) { + // All SGET/SPUTs refer to initialized classes. Nothing to do. + temp_insn_data_ = nullptr; + temp_scoped_alloc_.reset(); + return false; + } + + temp_bit_vector_size_ = unique_class_count; + temp_bit_vector_ = new (temp_scoped_alloc_.get()) ArenaBitVector( + temp_scoped_alloc_.get(), temp_bit_vector_size_, false, kBitMapClInitCheck); + DCHECK_GT(temp_bit_vector_size_, 0u); + return true; +} + +/* + * Eliminate unnecessary class initialization checks for a basic block. + */ +bool MIRGraph::EliminateClassInitChecks(BasicBlock* bb) { + DCHECK_EQ((cu_->disable_opt & (1 << kClassInitCheckElimination)), 0u); + if (bb->data_flow_info == NULL) { + return false; + } + + /* + * Set initial state. Catch blocks don't need any special treatment. + */ + ArenaBitVector* classes_to_check = temp_bit_vector_; + DCHECK(classes_to_check != nullptr); + if (bb->block_type == kEntryBlock) { + classes_to_check->SetInitialBits(temp_bit_vector_size_); + } else if (bb->predecessors->Size() == 1) { + BasicBlock* pred_bb = GetBasicBlock(bb->predecessors->Get(0)); + // pred_bb must have already been processed at least once. + DCHECK(pred_bb != nullptr); + DCHECK(pred_bb->data_flow_info != nullptr); + DCHECK(pred_bb->data_flow_info->ending_check_v != nullptr); + classes_to_check->Copy(pred_bb->data_flow_info->ending_check_v); + } else { + // Starting state is union of all incoming arcs + GrowableArray::Iterator iter(bb->predecessors); + BasicBlock* pred_bb = GetBasicBlock(iter.Next()); + DCHECK(pred_bb != NULL); + DCHECK(pred_bb->data_flow_info != NULL); + while (pred_bb->data_flow_info->ending_check_v == nullptr) { + pred_bb = GetBasicBlock(iter.Next()); + // At least one predecessor must have been processed before this bb. + DCHECK(pred_bb != nullptr); + DCHECK(pred_bb->data_flow_info != nullptr); + } + classes_to_check->Copy(pred_bb->data_flow_info->ending_check_v); + while (true) { + pred_bb = GetBasicBlock(iter.Next()); + if (!pred_bb) break; + DCHECK(pred_bb->data_flow_info != nullptr); + if (pred_bb->data_flow_info->ending_check_v == nullptr) { + continue; + } + classes_to_check->Union(pred_bb->data_flow_info->ending_check_v); + } + } + // At this point, classes_to_check shows which classes need clinit checks. + + // Walk through the instruction in the block, updating as necessary + for (MIR* mir = bb->first_mir_insn; mir != nullptr; mir = mir->next) { + if (mir->dalvikInsn.opcode >= Instruction::SGET && + mir->dalvikInsn.opcode <= Instruction::SPUT_SHORT) { + uint16_t index = temp_insn_data_[mir->offset / 2u]; + if (index != 0xffffu) { + if (mir->dalvikInsn.opcode >= Instruction::SGET && + mir->dalvikInsn.opcode <= Instruction::SPUT_SHORT) { + if (!classes_to_check->IsBitSet(index)) { + // Eliminate the class init check. + mir->optimization_flags |= MIR_IGNORE_CLINIT_CHECK; + } else { + // Do the class init check. + mir->optimization_flags &= ~MIR_IGNORE_CLINIT_CHECK; + } + } + // Mark the class as initialized. + classes_to_check->ClearBit(index); + } + } } // 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_); + bool changed = false; + if (bb->data_flow_info->ending_check_v == nullptr) { + DCHECK(temp_scoped_alloc_.get() != nullptr); + DCHECK(bb->data_flow_info != nullptr); + bb->data_flow_info->ending_check_v = new (temp_scoped_alloc_.get()) ArenaBitVector( + temp_scoped_alloc_.get(), temp_bit_vector_size_, false, kBitMapClInitCheck); + changed = classes_to_check->GetHighestBitSet() != -1; + bb->data_flow_info->ending_check_v->Copy(classes_to_check); + } else if (!classes_to_check->Equal(bb->data_flow_info->ending_check_v)) { + changed = true; + bb->data_flow_info->ending_check_v->Copy(classes_to_check); } 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); +void MIRGraph::EliminateClassInitChecksEnd() { + // Clean up temporaries. + temp_bit_vector_size_ = 0u; + temp_bit_vector_ = nullptr; + AllNodesIterator iter(this); + for (BasicBlock* bb = iter.Next(); bb != nullptr; bb = iter.Next()) { + if (bb->data_flow_info != nullptr) { + bb->data_flow_info->ending_check_v = nullptr; } } - if (cu_->enable_debug & (1 << kDebugDumpCFG)) { - DumpCFG("/sdcard/4_post_nce_cfg/", false); + + DCHECK(temp_insn_data_ != nullptr); + temp_insn_data_ = nullptr; + DCHECK(temp_scoped_alloc_.get() != nullptr); + temp_scoped_alloc_.reset(); +} + +bool MIRGraph::ApplyGlobalValueNumberingGate() { + if ((cu_->disable_opt & (1u << kGlobalValueNumbering)) != 0u) { + return false; } + + DCHECK(temp_scoped_alloc_ == nullptr); + temp_scoped_alloc_.reset(ScopedArenaAllocator::Create(&cu_->arena_stack)); + DCHECK(temp_gvn_ == nullptr); + temp_gvn_.reset( + new (temp_scoped_alloc_.get()) GlobalValueNumbering(cu_, temp_scoped_alloc_.get())); + return true; } -void MIRGraph::BasicBlockCombine() { - PreOrderDfsIterator iter(this, false /* not iterative */); - for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { - CombineBlocks(bb); +bool MIRGraph::ApplyGlobalValueNumbering(BasicBlock* bb) { + DCHECK(temp_gvn_ != nullptr); + LocalValueNumbering* lvn = temp_gvn_->PrepareBasicBlock(bb); + if (lvn != nullptr) { + for (MIR* mir = bb->first_mir_insn; mir != nullptr; mir = mir->next) { + lvn->GetValueNumber(mir); + } } - if (cu_->enable_debug & (1 << kDebugDumpCFG)) { - DumpCFG("/sdcard/5_post_bbcombine_cfg/", false); + bool change = (lvn != nullptr) && temp_gvn_->FinishBasicBlock(bb); + return change; +} + +void MIRGraph::ApplyGlobalValueNumberingEnd() { + // Perform modifications. + if (temp_gvn_->Good()) { + temp_gvn_->AllowModifications(); + PreOrderDfsIterator iter(this); + for (BasicBlock* bb = iter.Next(); bb != nullptr; bb = iter.Next()) { + ScopedArenaAllocator allocator(&cu_->arena_stack); // Reclaim memory after each LVN. + LocalValueNumbering* lvn = temp_gvn_->PrepareBasicBlock(bb, &allocator); + if (lvn != nullptr) { + for (MIR* mir = bb->first_mir_insn; mir != nullptr; mir = mir->next) { + lvn->GetValueNumber(mir); + } + bool change = temp_gvn_->FinishBasicBlock(bb); + DCHECK(!change) << PrettyMethod(cu_->method_idx, *cu_->dex_file); + } + } + } else { + LOG(WARNING) << "GVN failed for " << PrettyMethod(cu_->method_idx, *cu_->dex_file); } + + DCHECK(temp_gvn_ != nullptr); + temp_gvn_.reset(); + DCHECK(temp_scoped_alloc_ != nullptr); + temp_scoped_alloc_.reset(); } -void MIRGraph::CodeLayout() { - if (cu_->enable_debug & (1 << kDebugVerifyDataflow)) { - VerifyDataflow(); +void MIRGraph::ComputeInlineIFieldLoweringInfo(uint16_t field_idx, MIR* invoke, MIR* iget_or_iput) { + uint32_t method_index = invoke->meta.method_lowering_info; + if (temp_bit_vector_->IsBitSet(method_index)) { + iget_or_iput->meta.ifield_lowering_info = temp_insn_data_[method_index]; + DCHECK_EQ(field_idx, GetIFieldLoweringInfo(iget_or_iput).FieldIndex()); + return; } - AllNodesIterator iter(this, false /* not iterative */); - for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { - LayoutBlocks(bb); + + const MirMethodLoweringInfo& method_info = GetMethodLoweringInfo(invoke); + MethodReference target = method_info.GetTargetMethod(); + DexCompilationUnit inlined_unit( + cu_, cu_->class_loader, cu_->class_linker, *target.dex_file, + nullptr /* code_item not used */, 0u /* class_def_idx not used */, target.dex_method_index, + 0u /* access_flags not used */, nullptr /* verified_method not used */); + MirIFieldLoweringInfo inlined_field_info(field_idx); + MirIFieldLoweringInfo::Resolve(cu_->compiler_driver, &inlined_unit, &inlined_field_info, 1u); + DCHECK(inlined_field_info.IsResolved()); + + uint32_t field_info_index = ifield_lowering_infos_.Size(); + ifield_lowering_infos_.Insert(inlined_field_info); + temp_bit_vector_->SetBit(method_index); + temp_insn_data_[method_index] = field_info_index; + iget_or_iput->meta.ifield_lowering_info = field_info_index; +} + +bool MIRGraph::InlineSpecialMethodsGate() { + if ((cu_->disable_opt & (1 << kSuppressMethodInlining)) != 0 || + method_lowering_infos_.Size() == 0u) { + return false; } - if (cu_->enable_debug & (1 << kDebugDumpCFG)) { - DumpCFG("/sdcard/2_post_layout_cfg/", true); + if (cu_->compiler_driver->GetMethodInlinerMap() == nullptr) { + // This isn't the Quick compiler. + return false; } + return true; +} + +void MIRGraph::InlineSpecialMethodsStart() { + // Prepare for inlining getters/setters. Since we're inlining at most 1 IGET/IPUT from + // each INVOKE, we can index the data by the MIR::meta::method_lowering_info index. + + DCHECK(temp_scoped_alloc_.get() == nullptr); + temp_scoped_alloc_.reset(ScopedArenaAllocator::Create(&cu_->arena_stack)); + temp_bit_vector_size_ = method_lowering_infos_.Size(); + temp_bit_vector_ = new (temp_scoped_alloc_.get()) ArenaBitVector( + temp_scoped_alloc_.get(), temp_bit_vector_size_, false, kBitMapMisc); + temp_bit_vector_->ClearAllBits(); + temp_insn_data_ = static_cast(temp_scoped_alloc_->Alloc( + temp_bit_vector_size_ * sizeof(*temp_insn_data_), kArenaAllocGrowableArray)); +} + +void MIRGraph::InlineSpecialMethods(BasicBlock* bb) { + if (bb->block_type != kDalvikByteCode) { + return; + } + for (MIR* mir = bb->first_mir_insn; mir != NULL; mir = mir->next) { + if (MIR::DecodedInstruction::IsPseudoMirOp(mir->dalvikInsn.opcode)) { + continue; + } + if (!(Instruction::FlagsOf(mir->dalvikInsn.opcode) & Instruction::kInvoke)) { + continue; + } + const MirMethodLoweringInfo& method_info = GetMethodLoweringInfo(mir); + if (!method_info.FastPath()) { + continue; + } + InvokeType sharp_type = method_info.GetSharpType(); + if ((sharp_type != kDirect) && + (sharp_type != kStatic || method_info.NeedsClassInitialization())) { + continue; + } + DCHECK(cu_->compiler_driver->GetMethodInlinerMap() != nullptr); + MethodReference target = method_info.GetTargetMethod(); + if (cu_->compiler_driver->GetMethodInlinerMap()->GetMethodInliner(target.dex_file) + ->GenInline(this, bb, mir, target.dex_method_index)) { + if (cu_->verbose || cu_->print_pass) { + LOG(INFO) << "SpecialMethodInliner: Inlined " << method_info.GetInvokeType() << " (" + << sharp_type << ") call to \"" << PrettyMethod(target.dex_method_index, *target.dex_file) + << "\" from \"" << PrettyMethod(cu_->method_idx, *cu_->dex_file) + << "\" @0x" << std::hex << mir->offset; + } + } + } +} + +void MIRGraph::InlineSpecialMethodsEnd() { + DCHECK(temp_insn_data_ != nullptr); + temp_insn_data_ = nullptr; + DCHECK(temp_bit_vector_ != nullptr); + temp_bit_vector_ = nullptr; + DCHECK(temp_scoped_alloc_.get() != nullptr); + temp_scoped_alloc_.reset(); } void MIRGraph::DumpCheckStats() { Checkstats* stats = - static_cast(arena_->Alloc(sizeof(Checkstats), ArenaAllocator::kAllocDFInfo)); + static_cast(arena_->Alloc(sizeof(Checkstats), kArenaAllocDFInfo)); checkstats_ = stats; - AllNodesIterator iter(this, false /* not iterative */); + AllNodesIterator iter(this); for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { CountChecks(bb); } @@ -834,41 +1319,44 @@ bool MIRGraph::BuildExtendedBBList(struct BasicBlock* bb) { } // Must be head of extended basic block. BasicBlock* start_bb = bb; - extended_basic_blocks_.push_back(bb); + extended_basic_blocks_.push_back(bb->id); bool terminated_by_return = false; + bool do_local_value_numbering = false; // Visit blocks strictly dominated by this head. while (bb != NULL) { bb->visited = true; terminated_by_return |= bb->terminated_by_return; + do_local_value_numbering |= bb->use_lvn; bb = NextDominatedBlock(bb); } - if (terminated_by_return) { - // This extended basic block contains a return, so mark all members. + if (terminated_by_return || do_local_value_numbering) { + // Do lvn for all blocks in this extended set. bb = start_bb; while (bb != NULL) { - bb->dominates_return = true; + bb->use_lvn = do_local_value_numbering; + bb->dominates_return = terminated_by_return; 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); + if ((cu_->disable_opt & (1 << kSuppressExceptionEdges)) != 0) { ClearAllVisitedFlags(); - PreOrderDfsIterator iter2(this, false /* not iterative */); + PreOrderDfsIterator iter2(this); 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]); + BasicBlockOpt(GetBasicBlock(extended_basic_blocks_[i])); + } + } else { + PreOrderDfsIterator iter(this); + for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { + BasicBlockOpt(bb); } - } - if (cu_->enable_debug & (1 << kDebugDumpCFG)) { - DumpCFG("/sdcard/6_post_bbo_cfg/", false); } } diff --git a/compiler/dex/mir_optimization_test.cc b/compiler/dex/mir_optimization_test.cc new file mode 100644 index 00000000000..c510b528ff1 --- /dev/null +++ b/compiler/dex/mir_optimization_test.cc @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "compiler_internals.h" +#include "dataflow_iterator.h" +#include "dataflow_iterator-inl.h" +#include "gtest/gtest.h" + +namespace art { + +class ClassInitCheckEliminationTest : public testing::Test { + protected: + struct SFieldDef { + uint16_t field_idx; + uintptr_t declaring_dex_file; + uint16_t declaring_class_idx; + uint16_t declaring_field_idx; + }; + + struct BBDef { + static constexpr size_t kMaxSuccessors = 4; + static constexpr size_t kMaxPredecessors = 4; + + BBType type; + size_t num_successors; + BasicBlockId successors[kMaxPredecessors]; + size_t num_predecessors; + BasicBlockId predecessors[kMaxPredecessors]; + }; + + struct MIRDef { + Instruction::Code opcode; + BasicBlockId bbid; + uint32_t field_or_method_info; + }; + +#define DEF_SUCC0() \ + 0u, { } +#define DEF_SUCC1(s1) \ + 1u, { s1 } +#define DEF_SUCC2(s1, s2) \ + 2u, { s1, s2 } +#define DEF_SUCC3(s1, s2, s3) \ + 3u, { s1, s2, s3 } +#define DEF_SUCC4(s1, s2, s3, s4) \ + 4u, { s1, s2, s3, s4 } +#define DEF_PRED0() \ + 0u, { } +#define DEF_PRED1(p1) \ + 1u, { p1 } +#define DEF_PRED2(p1, p2) \ + 2u, { p1, p2 } +#define DEF_PRED3(p1, p2, p3) \ + 3u, { p1, p2, p3 } +#define DEF_PRED4(p1, p2, p3, p4) \ + 4u, { p1, p2, p3, p4 } +#define DEF_BB(type, succ, pred) \ + { type, succ, pred } + +#define DEF_MIR(opcode, bb, field_info) \ + { opcode, bb, field_info } + + void DoPrepareSFields(const SFieldDef* defs, size_t count) { + cu_.mir_graph->sfield_lowering_infos_.Reset(); + cu_.mir_graph->sfield_lowering_infos_.Resize(count); + for (size_t i = 0u; i != count; ++i) { + const SFieldDef* def = &defs[i]; + MirSFieldLoweringInfo field_info(def->field_idx); + if (def->declaring_dex_file != 0u) { + field_info.declaring_dex_file_ = reinterpret_cast(def->declaring_dex_file); + field_info.declaring_class_idx_ = def->declaring_class_idx; + field_info.declaring_field_idx_ = def->declaring_field_idx; + field_info.flags_ = MirSFieldLoweringInfo::kFlagIsStatic; + } + ASSERT_EQ(def->declaring_dex_file != 0u, field_info.IsResolved()); + ASSERT_FALSE(field_info.IsInitialized()); + cu_.mir_graph->sfield_lowering_infos_.Insert(field_info); + } + } + + template + void PrepareSFields(const SFieldDef (&defs)[count]) { + DoPrepareSFields(defs, count); + } + + void DoPrepareBasicBlocks(const BBDef* defs, size_t count) { + cu_.mir_graph->block_id_map_.clear(); + cu_.mir_graph->block_list_.Reset(); + ASSERT_LT(3u, count); // null, entry, exit and at least one bytecode block. + ASSERT_EQ(kNullBlock, defs[0].type); + ASSERT_EQ(kEntryBlock, defs[1].type); + ASSERT_EQ(kExitBlock, defs[2].type); + for (size_t i = 0u; i != count; ++i) { + const BBDef* def = &defs[i]; + BasicBlock* bb = cu_.mir_graph->NewMemBB(def->type, i); + cu_.mir_graph->block_list_.Insert(bb); + if (def->num_successors <= 2) { + bb->successor_block_list_type = kNotUsed; + bb->successor_blocks = nullptr; + bb->fall_through = (def->num_successors >= 1) ? def->successors[0] : 0u; + bb->taken = (def->num_successors >= 2) ? def->successors[1] : 0u; + } else { + bb->successor_block_list_type = kPackedSwitch; + bb->fall_through = 0u; + bb->taken = 0u; + bb->successor_blocks = new (&cu_.arena) GrowableArray( + &cu_.arena, def->num_successors, kGrowableArraySuccessorBlocks); + for (size_t j = 0u; j != def->num_successors; ++j) { + SuccessorBlockInfo* successor_block_info = + static_cast(cu_.arena.Alloc(sizeof(SuccessorBlockInfo), + kArenaAllocSuccessor)); + successor_block_info->block = j; + successor_block_info->key = 0u; // Not used by class init check elimination. + bb->successor_blocks->Insert(successor_block_info); + } + } + bb->predecessors = new (&cu_.arena) GrowableArray( + &cu_.arena, def->num_predecessors, kGrowableArrayPredecessors); + for (size_t j = 0u; j != def->num_predecessors; ++j) { + ASSERT_NE(0u, def->predecessors[j]); + bb->predecessors->Insert(def->predecessors[j]); + } + if (def->type == kDalvikByteCode || def->type == kEntryBlock || def->type == kExitBlock) { + bb->data_flow_info = static_cast( + cu_.arena.Alloc(sizeof(BasicBlockDataFlow), kArenaAllocDFInfo)); + } + } + cu_.mir_graph->num_blocks_ = count; + ASSERT_EQ(count, cu_.mir_graph->block_list_.Size()); + cu_.mir_graph->entry_block_ = cu_.mir_graph->block_list_.Get(1); + ASSERT_EQ(kEntryBlock, cu_.mir_graph->entry_block_->block_type); + cu_.mir_graph->exit_block_ = cu_.mir_graph->block_list_.Get(2); + ASSERT_EQ(kExitBlock, cu_.mir_graph->exit_block_->block_type); + } + + template + void PrepareBasicBlocks(const BBDef (&defs)[count]) { + DoPrepareBasicBlocks(defs, count); + } + + void DoPrepareMIRs(const MIRDef* defs, size_t count) { + mir_count_ = count; + mirs_ = reinterpret_cast(cu_.arena.Alloc(sizeof(MIR) * count, kArenaAllocMIR)); + uint64_t merged_df_flags = 0u; + for (size_t i = 0u; i != count; ++i) { + const MIRDef* def = &defs[i]; + MIR* mir = &mirs_[i]; + mir->dalvikInsn.opcode = def->opcode; + ASSERT_LT(def->bbid, cu_.mir_graph->block_list_.Size()); + BasicBlock* bb = cu_.mir_graph->block_list_.Get(def->bbid); + bb->AppendMIR(mir); + if (def->opcode >= Instruction::SGET && def->opcode <= Instruction::SPUT_SHORT) { + ASSERT_LT(def->field_or_method_info, cu_.mir_graph->sfield_lowering_infos_.Size()); + mir->meta.sfield_lowering_info = def->field_or_method_info; + } + mir->ssa_rep = nullptr; + mir->offset = 2 * i; // All insns need to be at least 2 code units long. + mir->optimization_flags = 0u; + merged_df_flags |= MIRGraph::GetDataFlowAttributes(def->opcode); + } + cu_.mir_graph->merged_df_flags_ = merged_df_flags; + + code_item_ = static_cast( + cu_.arena.Alloc(sizeof(DexFile::CodeItem), kArenaAllocMisc)); + memset(code_item_, 0, sizeof(DexFile::CodeItem)); + code_item_->insns_size_in_code_units_ = 2u * count; + cu_.mir_graph->current_code_item_ = cu_.code_item = code_item_; + } + + template + void PrepareMIRs(const MIRDef (&defs)[count]) { + DoPrepareMIRs(defs, count); + } + + void PerformClassInitCheckElimination() { + cu_.mir_graph->SSATransformationStart(); + cu_.mir_graph->ComputeDFSOrders(); + cu_.mir_graph->ComputeDominators(); + cu_.mir_graph->ComputeTopologicalSortOrder(); + cu_.mir_graph->SSATransformationEnd(); + bool gate_result = cu_.mir_graph->EliminateClassInitChecksGate(); + ASSERT_TRUE(gate_result); + LoopRepeatingTopologicalSortIterator iterator(cu_.mir_graph.get()); + bool change = false; + for (BasicBlock* bb = iterator.Next(change); bb != nullptr; bb = iterator.Next(change)) { + change = cu_.mir_graph->EliminateClassInitChecks(bb); + } + cu_.mir_graph->EliminateClassInitChecksEnd(); + } + + ClassInitCheckEliminationTest() + : pool_(), + cu_(&pool_), + mir_count_(0u), + mirs_(nullptr), + code_item_(nullptr) { + cu_.mir_graph.reset(new MIRGraph(&cu_, &cu_.arena)); + } + + ArenaPool pool_; + CompilationUnit cu_; + size_t mir_count_; + MIR* mirs_; + DexFile::CodeItem* code_item_; +}; + +TEST_F(ClassInitCheckEliminationTest, SingleBlock) { + static const SFieldDef sfields[] = { + { 0u, 1u, 0u, 0u }, + { 1u, 1u, 1u, 1u }, + { 2u, 1u, 2u, 2u }, + { 3u, 1u, 3u, 3u }, // Same declaring class as sfield[4]. + { 4u, 1u, 3u, 4u }, // Same declaring class as sfield[3]. + { 5u, 0u, 0u, 0u }, // Unresolved. + }; + static const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(3)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(1)), + }; + static const MIRDef mirs[] = { + DEF_MIR(Instruction::SPUT, 3u, 5u), // Unresolved. + DEF_MIR(Instruction::SPUT, 3u, 0u), + DEF_MIR(Instruction::SGET, 3u, 1u), + DEF_MIR(Instruction::SGET, 3u, 2u), + DEF_MIR(Instruction::SGET, 3u, 5u), // Unresolved. + DEF_MIR(Instruction::SGET, 3u, 0u), + DEF_MIR(Instruction::SGET, 3u, 1u), + DEF_MIR(Instruction::SGET, 3u, 2u), + DEF_MIR(Instruction::SGET, 3u, 5u), // Unresolved. + DEF_MIR(Instruction::SGET, 3u, 3u), + DEF_MIR(Instruction::SGET, 3u, 4u), + }; + static const bool expected_ignore_clinit_check[] = { + false, false, false, false, true, true, true, true, true, false, true + }; + + PrepareSFields(sfields); + PrepareBasicBlocks(bbs); + PrepareMIRs(mirs); + PerformClassInitCheckElimination(); + ASSERT_EQ(arraysize(expected_ignore_clinit_check), mir_count_); + for (size_t i = 0u; i != arraysize(mirs); ++i) { + EXPECT_EQ(expected_ignore_clinit_check[i], + (mirs_[i].optimization_flags & MIR_IGNORE_CLINIT_CHECK) != 0) << i; + } +} + +TEST_F(ClassInitCheckEliminationTest, Diamond) { + static const SFieldDef sfields[] = { + { 0u, 1u, 0u, 0u }, + { 1u, 1u, 1u, 1u }, + { 2u, 1u, 2u, 2u }, + { 3u, 1u, 3u, 3u }, + { 4u, 1u, 4u, 4u }, + { 5u, 1u, 5u, 5u }, + { 6u, 1u, 6u, 6u }, + { 7u, 1u, 7u, 7u }, + { 8u, 1u, 8u, 8u }, // Same declaring class as sfield[9]. + { 9u, 1u, 8u, 9u }, // Same declaring class as sfield[8]. + { 10u, 0u, 0u, 0u }, // Unresolved. + }; + static const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(6)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(4, 5), DEF_PRED1(1)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(6), DEF_PRED1(3)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(6), DEF_PRED1(3)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED2(4, 5)), + }; + static const MIRDef mirs[] = { + // NOTE: MIRs here are ordered by unique tests. They will be put into appropriate blocks. + DEF_MIR(Instruction::SGET, 3u, 10u), // Unresolved. + DEF_MIR(Instruction::SPUT, 3u, 10u), // Unresolved. + DEF_MIR(Instruction::SPUT, 3u, 0u), + DEF_MIR(Instruction::SGET, 6u, 0u), // Eliminated (block #3 dominates #6). + DEF_MIR(Instruction::SPUT, 4u, 1u), + DEF_MIR(Instruction::SGET, 6u, 1u), // Not eliminated (block #4 doesn't dominate #6). + DEF_MIR(Instruction::SGET, 3u, 2u), + DEF_MIR(Instruction::SGET, 4u, 2u), // Eliminated (block #3 dominates #4). + DEF_MIR(Instruction::SGET, 3u, 3u), + DEF_MIR(Instruction::SGET, 5u, 3u), // Eliminated (block #3 dominates #5). + DEF_MIR(Instruction::SGET, 3u, 4u), + DEF_MIR(Instruction::SGET, 6u, 4u), // Eliminated (block #3 dominates #6). + DEF_MIR(Instruction::SGET, 4u, 5u), + DEF_MIR(Instruction::SGET, 6u, 5u), // Not eliminated (block #4 doesn't dominate #6). + DEF_MIR(Instruction::SGET, 5u, 6u), + DEF_MIR(Instruction::SGET, 6u, 6u), // Not eliminated (block #5 doesn't dominate #6). + DEF_MIR(Instruction::SGET, 4u, 7u), + DEF_MIR(Instruction::SGET, 5u, 7u), + DEF_MIR(Instruction::SGET, 6u, 7u), // Eliminated (initialized in both blocks #3 and #4). + DEF_MIR(Instruction::SGET, 4u, 8u), + DEF_MIR(Instruction::SGET, 5u, 9u), + DEF_MIR(Instruction::SGET, 6u, 8u), // Eliminated (with sfield[9] in block #5). + DEF_MIR(Instruction::SPUT, 6u, 9u), // Eliminated (with sfield[8] in block #4). + }; + static const bool expected_ignore_clinit_check[] = { + false, true, // Unresolved: sfield[10], method[2] + false, true, // sfield[0] + false, false, // sfield[1] + false, true, // sfield[2] + false, true, // sfield[3] + false, true, // sfield[4] + false, false, // sfield[5] + false, false, // sfield[6] + false, false, true, // sfield[7] + false, false, true, true, // sfield[8], sfield[9] + }; + + PrepareSFields(sfields); + PrepareBasicBlocks(bbs); + PrepareMIRs(mirs); + PerformClassInitCheckElimination(); + ASSERT_EQ(arraysize(expected_ignore_clinit_check), mir_count_); + for (size_t i = 0u; i != arraysize(mirs); ++i) { + EXPECT_EQ(expected_ignore_clinit_check[i], + (mirs_[i].optimization_flags & MIR_IGNORE_CLINIT_CHECK) != 0) << i; + } +} + +TEST_F(ClassInitCheckEliminationTest, Loop) { + static const SFieldDef sfields[] = { + { 0u, 1u, 0u, 0u }, + { 1u, 1u, 1u, 1u }, + }; + static const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(5)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(1)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(5, 4), DEF_PRED2(3, 4)), // "taken" loops to self. + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(4)), + }; + static const MIRDef mirs[] = { + DEF_MIR(Instruction::SGET, 3u, 0u), + DEF_MIR(Instruction::SGET, 4u, 1u), + DEF_MIR(Instruction::SGET, 5u, 0u), // Eliminated. + DEF_MIR(Instruction::SGET, 5u, 1u), // Eliminated. + }; + static const bool expected_ignore_clinit_check[] = { + false, false, true, true + }; + + PrepareSFields(sfields); + PrepareBasicBlocks(bbs); + PrepareMIRs(mirs); + PerformClassInitCheckElimination(); + ASSERT_EQ(arraysize(expected_ignore_clinit_check), mir_count_); + for (size_t i = 0u; i != arraysize(mirs); ++i) { + EXPECT_EQ(expected_ignore_clinit_check[i], + (mirs_[i].optimization_flags & MIR_IGNORE_CLINIT_CHECK) != 0) << i; + } +} + +TEST_F(ClassInitCheckEliminationTest, Catch) { + static const SFieldDef sfields[] = { + { 0u, 1u, 0u, 0u }, + { 1u, 1u, 1u, 1u }, + { 2u, 1u, 2u, 2u }, + { 3u, 1u, 3u, 3u }, + }; + static const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(6)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(1)), // The top. + DEF_BB(kDalvikByteCode, DEF_SUCC1(6), DEF_PRED1(3)), // The throwing insn. + DEF_BB(kDalvikByteCode, DEF_SUCC1(6), DEF_PRED1(3)), // Catch handler. + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED2(4, 5)), // The merged block. + }; + static const MIRDef mirs[] = { + DEF_MIR(Instruction::SGET, 3u, 0u), // Before the exception edge. + DEF_MIR(Instruction::SGET, 3u, 1u), // Before the exception edge. + DEF_MIR(Instruction::SGET, 4u, 2u), // After the exception edge. + DEF_MIR(Instruction::SGET, 4u, 3u), // After the exception edge. + DEF_MIR(Instruction::SGET, 5u, 0u), // In catch handler; class init check eliminated. + DEF_MIR(Instruction::SGET, 5u, 2u), // In catch handler; class init check not eliminated. + DEF_MIR(Instruction::SGET, 6u, 0u), // Class init check eliminated. + DEF_MIR(Instruction::SGET, 6u, 1u), // Class init check eliminated. + DEF_MIR(Instruction::SGET, 6u, 2u), // Class init check eliminated. + DEF_MIR(Instruction::SGET, 6u, 3u), // Class init check not eliminated. + }; + static const bool expected_ignore_clinit_check[] = { + false, false, false, false, true, false, true, true, true, false + }; + + PrepareSFields(sfields); + PrepareBasicBlocks(bbs); + BasicBlock* catch_handler = cu_.mir_graph->GetBasicBlock(5u); + catch_handler->catch_entry = true; + // Add successor block info to the check block. + BasicBlock* check_bb = cu_.mir_graph->GetBasicBlock(3u); + check_bb->successor_block_list_type = kCatch; + check_bb->successor_blocks = new (&cu_.arena) GrowableArray( + &cu_.arena, 2, kGrowableArraySuccessorBlocks); + SuccessorBlockInfo* successor_block_info = reinterpret_cast + (cu_.arena.Alloc(sizeof(SuccessorBlockInfo), kArenaAllocSuccessor)); + successor_block_info->block = catch_handler->id; + check_bb->successor_blocks->Insert(successor_block_info); + PrepareMIRs(mirs); + PerformClassInitCheckElimination(); + ASSERT_EQ(arraysize(expected_ignore_clinit_check), mir_count_); + for (size_t i = 0u; i != arraysize(mirs); ++i) { + EXPECT_EQ(expected_ignore_clinit_check[i], + (mirs_[i].optimization_flags & MIR_IGNORE_CLINIT_CHECK) != 0) << i; + } +} + +} // namespace art diff --git a/compiler/dex/pass.h b/compiler/dex/pass.h new file mode 100644 index 00000000000..dbb5366af6e --- /dev/null +++ b/compiler/dex/pass.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_DEX_PASS_H_ +#define ART_COMPILER_DEX_PASS_H_ + +#include + +#include "base/macros.h" +namespace art { + +// Forward declarations. +struct BasicBlock; +struct CompilationUnit; +class Pass; + +// Empty Pass Data Class, can be extended by any pass extending the base Pass class. +class PassDataHolder { +}; + +/** + * @class Pass + * @brief Base Pass class, can be extended to perform a more defined way of doing the work call. + */ +class Pass { + public: + explicit Pass(const char* name) + : pass_name_(name) { + } + + virtual ~Pass() { + } + + virtual const char* GetName() const { + return pass_name_; + } + + /** + * @brief Gate for the pass: determines whether to execute the pass or not considering a CompilationUnit + * @param data the PassDataHolder. + * @return whether or not to execute the pass. + */ + virtual bool Gate(const PassDataHolder* data) const { + // Unused parameter. + UNUSED(data); + + // Base class says yes. + return true; + } + + /** + * @brief Start of the pass: called before the Worker function. + */ + virtual void Start(PassDataHolder* data) const { + // Unused parameter. + UNUSED(data); + } + + /** + * @brief End of the pass: called after the WalkBasicBlocks function. + */ + virtual void End(PassDataHolder* data) const { + // Unused parameter. + UNUSED(data); + } + + /** + * @param data the object containing data necessary for the pass. + * @return whether or not there is a change when walking the BasicBlock + */ + virtual bool Worker(const PassDataHolder* data) const { + // Unused parameter. + UNUSED(data); + + // BasicBlock did not change. + return false; + } + + static void BasePrintMessage(CompilationUnit* c_unit, const char* pass_name, const char* message, ...) { + // Check if we want to log something or not. + if (c_unit->print_pass) { + // Stringify the message. + va_list args; + va_start(args, message); + std::string stringified_message; + StringAppendV(&stringified_message, message, args); + va_end(args); + + // Log the message and ensure to include pass name. + LOG(INFO) << pass_name << ": " << stringified_message; + } + } + + protected: + /** @brief The pass name: used for searching for a pass when running a particular pass or debugging. */ + const char* const pass_name_; + + private: + // In order to make the all passes not copy-friendly. + DISALLOW_COPY_AND_ASSIGN(Pass); +}; +} // namespace art +#endif // ART_COMPILER_DEX_PASS_H_ diff --git a/compiler/dex/pass_driver.h b/compiler/dex/pass_driver.h new file mode 100644 index 00000000000..bc5913c0431 --- /dev/null +++ b/compiler/dex/pass_driver.h @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_DEX_PASS_DRIVER_H_ +#define ART_COMPILER_DEX_PASS_DRIVER_H_ + +#include +#include "pass.h" +#include "safe_map.h" + +#ifdef QC_STRONG +#define QC_WEAK +#else +#define QC_WEAK __attribute__((weak)) +#endif + +// Forward Declarations. +class Pass; +class PassDriver; +namespace art { +/** + * @brief Helper function to create a single instance of a given Pass and can be shared across + * the threads. + */ +template +const Pass* GetPassInstance() { + static const PassType pass; + return &pass; +} + +const Pass* GetMorePassInstance() QC_WEAK; + +// Empty holder for the constructor. +class PassDriverDataHolder { +}; + +/** + * @class PassDriver + * @brief PassDriver is the wrapper around all Pass instances in order to execute them + */ +template +class PassDriver { + public: + explicit PassDriver() { + InitializePasses(); + } + + virtual ~PassDriver() { + } + + /** + * @brief Insert a Pass: can warn if multiple passes have the same name. + */ + void InsertPass(const Pass* new_pass) { + DCHECK(new_pass != nullptr); + DCHECK(new_pass->GetName() != nullptr && new_pass->GetName()[0] != 0); + + // It is an error to override an existing pass. + DCHECK(GetPass(new_pass->GetName()) == nullptr) + << "Pass name " << new_pass->GetName() << " already used."; + + // Now add to the list. + pass_list_.push_back(new_pass); + } + + /** + * @brief Run a pass using the name as key. + * @return whether the pass was applied. + */ + virtual bool RunPass(const char* pass_name) { + // Paranoid: c_unit cannot be nullptr and we need a pass name. + DCHECK(pass_name != nullptr && pass_name[0] != 0); + + const Pass* cur_pass = GetPass(pass_name); + + if (cur_pass != nullptr) { + return RunPass(cur_pass); + } + + // Return false, we did not find the pass. + return false; + } + + /** + * @brief Runs all the passes with the pass_list_. + */ + void Launch() { + for (const Pass* cur_pass : pass_list_) { + RunPass(cur_pass); + } + } + + /** + * @brief Searches for a particular pass. + * @param the name of the pass to be searched for. + */ + const Pass* GetPass(const char* name) const { + for (const Pass* cur_pass : pass_list_) { + if (strcmp(name, cur_pass->GetName()) == 0) { + return cur_pass; + } + } + return nullptr; + } + + static void CreateDefaultPassList(const std::string& disable_passes) { + // Insert each pass from g_passes into g_default_pass_list. + PassDriverType::g_default_pass_list.clear(); + PassDriverType::g_default_pass_list.reserve(PassDriver::g_passes_size); + for (uint16_t i = 0; i < PassDriver::g_passes_size; ++i) { + const Pass* pass = PassDriver::g_passes[i]; + // Check if we should disable this pass. + if (disable_passes.find(pass->GetName()) != std::string::npos) { + LOG(INFO) << "Skipping " << pass->GetName(); + } else { + PassDriver::g_default_pass_list.push_back(pass); + } + } + } + + /** + * @brief Run a pass using the Pass itself. + * @param time_split do we want a time split request(default: false)? + * @return whether the pass was applied. + */ + virtual bool RunPass(const Pass* pass, bool time_split = false) = 0; + + /** + * @brief Print the pass names of all the passes available. + */ + static void PrintPassNames() { + LOG(INFO) << "Loop Passes are:"; + + for (const Pass* cur_pass : PassDriver::g_default_pass_list) { + LOG(INFO) << "\t-" << cur_pass->GetName(); + } + } + + /** + * @brief Gets the list of passes currently schedule to execute. + * @return pass_list_ + */ + std::vector& GetPasses() { + return pass_list_; + } + + static void SetPrintAllPasses() { + default_print_passes_ = true; + } + + static void SetDumpPassList(const std::string& list) { + dump_pass_list_ = list; + } + + static void SetPrintPassList(const std::string& list) { + print_pass_list_ = list; + } + + void SetDefaultPasses() { + pass_list_ = PassDriver::g_default_pass_list; + } + + protected: + virtual void InitializePasses() { + SetDefaultPasses(); + } + + /** + * @brief Apply a patch: perform start/work/end functions. + */ + virtual void ApplyPass(PassDataHolder* data, const Pass* pass) { + pass->Start(data); + DispatchPass(pass); + pass->End(data); + } + /** + * @brief Dispatch a patch. + * Gives the ability to add logic when running the patch. + */ + virtual void DispatchPass(const Pass* pass) { + UNUSED(pass); + } + + /** @brief List of passes: provides the order to execute the passes. */ + std::vector pass_list_; + + /** @brief The number of passes within g_passes. */ + static const uint16_t g_passes_size; + + /** @brief The number of passes within g_passes. */ + static const Pass* const g_passes[]; + + /** @brief The default pass list is used to initialize pass_list_. */ + static std::vector g_default_pass_list; + + /** @brief Do we, by default, want to be printing the log messages? */ + static bool default_print_passes_; + + /** @brief What are the passes we want to be printing the log messages? */ + static std::string print_pass_list_; + + /** @brief What are the passes we want to be dumping the CFG? */ + static std::string dump_pass_list_; +}; + +} // namespace art +#endif // ART_COMPILER_DEX_PASS_DRIVER_H_ diff --git a/compiler/dex/pass_driver_me.h b/compiler/dex/pass_driver_me.h new file mode 100644 index 00000000000..133593ceb9d --- /dev/null +++ b/compiler/dex/pass_driver_me.h @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_DEX_PASS_DRIVER_ME_H_ +#define ART_COMPILER_DEX_PASS_DRIVER_ME_H_ + +#include "bb_optimizations.h" +#include "dataflow_iterator.h" +#include "dataflow_iterator-inl.h" +#include "pass_driver.h" +#include "pass_me.h" + +namespace art { + +template +class PassDriverME: public PassDriver { + public: + explicit PassDriverME(CompilationUnit* cu) + : pass_me_data_holder_(), dump_cfg_folder_("/sdcard/") { + pass_me_data_holder_.bb = nullptr; + pass_me_data_holder_.c_unit = cu; + } + + ~PassDriverME() { + } + + void DispatchPass(const Pass* pass) { + VLOG(compiler) << "Dispatching " << pass->GetName(); + const PassME* me_pass = down_cast(pass); + + DataFlowAnalysisMode mode = me_pass->GetTraversal(); + + switch (mode) { + case kPreOrderDFSTraversal: + DoWalkBasicBlocks(&pass_me_data_holder_, me_pass); + break; + case kRepeatingPreOrderDFSTraversal: + DoWalkBasicBlocks(&pass_me_data_holder_, me_pass); + break; + case kRepeatingPostOrderDFSTraversal: + DoWalkBasicBlocks(&pass_me_data_holder_, me_pass); + break; + case kReversePostOrderDFSTraversal: + DoWalkBasicBlocks(&pass_me_data_holder_, me_pass); + break; + case kRepeatingReversePostOrderDFSTraversal: + DoWalkBasicBlocks(&pass_me_data_holder_, me_pass); + break; + case kPostOrderDOMTraversal: + DoWalkBasicBlocks(&pass_me_data_holder_, me_pass); + break; + case kTopologicalSortTraversal: + DoWalkBasicBlocks(&pass_me_data_holder_, me_pass); + break; + case kRepeatingTopologicalSortTraversal: + DoWalkBasicBlocks(&pass_me_data_holder_, me_pass); + break; + case kLoopRepeatingTopologicalSortTraversal: + DoWalkBasicBlocks(&pass_me_data_holder_, me_pass); + break; + case kAllNodes: + DoWalkBasicBlocks(&pass_me_data_holder_, me_pass); + break; + case kNoNodes: + break; + default: + LOG(FATAL) << "Iterator mode not handled in dispatcher: " << mode; + break; + } + } + + bool RunPass(const Pass* pass, bool time_split) { + // Paranoid: c_unit and pass cannot be nullptr, and the pass should have a name + DCHECK(pass != nullptr); + DCHECK(pass->GetName() != nullptr && pass->GetName()[0] != 0); + CompilationUnit* c_unit = pass_me_data_holder_.c_unit; + DCHECK(c_unit != nullptr); + + // Do we perform a time split + if (time_split) { + c_unit->NewTimingSplit(pass->GetName()); + } + + // Check the pass gate first. + bool should_apply_pass = pass->Gate(&pass_me_data_holder_); + if (should_apply_pass) { + bool old_print_pass = c_unit->print_pass; + + c_unit->print_pass = PassDriver::default_print_passes_; + + const char* print_pass_list = PassDriver::print_pass_list_.c_str(); + + if (print_pass_list != nullptr && strstr(print_pass_list, pass->GetName()) != nullptr) { + c_unit->print_pass = true; + } + + // Applying the pass: first start, doWork, and end calls. + this->ApplyPass(&pass_me_data_holder_, pass); + + bool should_dump = ((c_unit->enable_debug & (1 << kDebugDumpCFG)) != 0); + + const char* dump_pass_list = PassDriver::dump_pass_list_.c_str(); + + if (dump_pass_list != nullptr) { + bool found = strstr(dump_pass_list, pass->GetName()); + should_dump = (should_dump || found); + } + + if (should_dump) { + // Do we want to log it? + if ((c_unit->enable_debug& (1 << kDebugDumpCFG)) != 0) { + // Do we have a pass folder? + const PassME* me_pass = (down_cast(pass)); + const char* passFolder = me_pass->GetDumpCFGFolder(); + DCHECK(passFolder != nullptr); + + if (passFolder[0] != 0) { + // Create directory prefix. + std::string prefix = GetDumpCFGFolder(); + prefix += passFolder; + prefix += "/"; + + c_unit->mir_graph->DumpCFG(prefix.c_str(), false); + } + } + } + + c_unit->print_pass = old_print_pass; + } + + // If the pass gate passed, we can declare success. + return should_apply_pass; + } + + const char* GetDumpCFGFolder() const { + return dump_cfg_folder_; + } + + protected: + /** @brief The data holder that contains data needed for the PassDriverME. */ + PassMEDataHolder pass_me_data_holder_; + + /** @brief Dump CFG base folder: where is the base folder for dumping CFGs. */ + const char* dump_cfg_folder_; + + static void DoWalkBasicBlocks(PassMEDataHolder* data, const PassME* pass, + DataflowIterator* iterator) { + // Paranoid: Check the iterator before walking the BasicBlocks. + DCHECK(iterator != nullptr); + bool change = false; + for (BasicBlock* bb = iterator->Next(change); bb != nullptr; bb = iterator->Next(change)) { + data->bb = bb; + change = pass->Worker(data); + } + } + + template + inline static void DoWalkBasicBlocks(PassMEDataHolder* data, const PassME* pass) { + DCHECK(data != nullptr); + CompilationUnit* c_unit = data->c_unit; + DCHECK(c_unit != nullptr); + Iterator iterator(c_unit->mir_graph.get()); + DoWalkBasicBlocks(data, pass, &iterator); + } +}; +} // namespace art +#endif // ART_COMPILER_DEX_PASS_DRIVER_ME_H_ + diff --git a/compiler/dex/pass_driver_me_opts.cc b/compiler/dex/pass_driver_me_opts.cc new file mode 100644 index 00000000000..d003cf918df --- /dev/null +++ b/compiler/dex/pass_driver_me_opts.cc @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/macros.h" +#include "bb_optimizations.h" +#include "compiler_internals.h" +#include "dataflow_iterator.h" +#include "dataflow_iterator-inl.h" +#include "pass_driver_me_opts.h" + +namespace art { + +const Pass* GetMorePassInstance() { + static const DummyPass pass; + return &pass; +} +/* + * Create the pass list. These passes are immutable and are shared across the threads. + * + * Advantage is that there will be no race conditions here. + * Disadvantage is the passes can't change their internal states depending on CompilationUnit: + * - This is not yet an issue: no current pass would require it. + */ +// The initial list of passes to be used by the PassDriveMEOpts. +template<> +const Pass* const PassDriver::g_passes[] = { + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetMorePassInstance(), +}; + +// The number of the passes in the initial list of Passes (g_passes). +template<> +uint16_t const PassDriver::g_passes_size = + arraysize(PassDriver::g_passes); + +// The default pass list is used by the PassDriverME instance of PassDriver +// to initialize pass_list_. +template<> +std::vector PassDriver::g_default_pass_list( + PassDriver::g_passes, + PassDriver::g_passes + + PassDriver::g_passes_size); + +// By default, do not have a dump pass list. +template<> +std::string PassDriver::dump_pass_list_ = std::string(); + +// By default, do not have a print pass list. +template<> +std::string PassDriver::print_pass_list_ = std::string(); + +// By default, we do not print the pass' information. +template<> +bool PassDriver::default_print_passes_ = false; + +void PassDriverMEOpts::ApplyPass(PassDataHolder* data, const Pass* pass) { + // First call the base class' version. + PassDriver::ApplyPass(data, pass); + + const PassME* pass_me = down_cast (pass); + DCHECK(pass_me != nullptr); + + PassMEDataHolder* pass_me_data_holder = down_cast(data); + + // Now we care about flags. + if ((pass_me->GetFlag(kOptimizationBasicBlockChange) == true) || + (pass_me->GetFlag(kOptimizationDefUsesChange) == true)) { + CompilationUnit* c_unit = pass_me_data_holder->c_unit; + c_unit->mir_graph.get()->CalculateBasicBlockInformation(); + } +} + +} // namespace art diff --git a/compiler/dex/pass_driver_me_opts.h b/compiler/dex/pass_driver_me_opts.h new file mode 100644 index 00000000000..0a5b5aec991 --- /dev/null +++ b/compiler/dex/pass_driver_me_opts.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_DEX_PASS_DRIVER_ME_OPTS_H_ +#define ART_COMPILER_DEX_PASS_DRIVER_ME_OPTS_H_ + +#include "pass_driver_me.h" + +namespace art { + +// Forward Declarations. +struct CompilationUnit; +class Pass; +class PassDataHolder; + +class PassDriverMEOpts : public PassDriverME { + public: + explicit PassDriverMEOpts(CompilationUnit* cu):PassDriverME(cu) { + } + + ~PassDriverMEOpts() { + } + + /** + * @brief Apply a patch: perform start/work/end functions. + */ + virtual void ApplyPass(PassDataHolder* data, const Pass* pass); +}; + +} // namespace art +#endif // ART_COMPILER_DEX_PASS_DRIVER_ME_OPTS_H_ diff --git a/compiler/dex/pass_driver_me_post_opt.cc b/compiler/dex/pass_driver_me_post_opt.cc new file mode 100644 index 00000000000..14108af6329 --- /dev/null +++ b/compiler/dex/pass_driver_me_post_opt.cc @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/macros.h" +#include "post_opt_passes.h" +#include "compiler_internals.h" +#include "pass_driver_me_post_opt.h" + +namespace art { + +/* + * Create the pass list. These passes are immutable and are shared across the threads. + * + * Advantage is that there will be no race conditions here. + * Disadvantage is the passes can't change their internal states depending on CompilationUnit: + * - This is not yet an issue: no current pass would require it. + */ +// The initial list of passes to be used by the PassDriveMEPostOpt. +template<> +const Pass* const PassDriver::g_passes[] = { + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), + GetPassInstance(), +}; + +// The number of the passes in the initial list of Passes (g_passes). +template<> +uint16_t const PassDriver::g_passes_size = + arraysize(PassDriver::g_passes); + +// The default pass list is used by the PassDriverME instance of PassDriver +// to initialize pass_list_. +template<> +std::vector PassDriver::g_default_pass_list( + PassDriver::g_passes, + PassDriver::g_passes + + PassDriver::g_passes_size); + +// By default, do not have a dump pass list. +template<> +std::string PassDriver::dump_pass_list_ = std::string(); + +// By default, do not have a print pass list. +template<> +std::string PassDriver::print_pass_list_ = std::string(); + +// By default, we do not print the pass' information. +template<> +bool PassDriver::default_print_passes_ = false; + +} // namespace art diff --git a/compiler/dex/pass_driver_me_post_opt.h b/compiler/dex/pass_driver_me_post_opt.h new file mode 100644 index 00000000000..574a6ba04d7 --- /dev/null +++ b/compiler/dex/pass_driver_me_post_opt.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_DEX_PASS_DRIVER_ME_POST_OPT_H_ +#define ART_COMPILER_DEX_PASS_DRIVER_ME_POST_OPT_H_ + +#include "pass_driver_me.h" + +namespace art { + +// Forward Declarations. +struct CompilationUnit; +class Pass; +class PassDataHolder; + +class PassDriverMEPostOpt : public PassDriverME { + public: + explicit PassDriverMEPostOpt(CompilationUnit* cu) : PassDriverME(cu) { + } + + ~PassDriverMEPostOpt() { + } +}; + +} // namespace art +#endif // ART_COMPILER_DEX_PASS_DRIVER_ME_POST_OPT_H_ diff --git a/compiler/dex/pass_me.h b/compiler/dex/pass_me.h new file mode 100644 index 00000000000..c7276eb905d --- /dev/null +++ b/compiler/dex/pass_me.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_DEX_PASS_ME_H_ +#define ART_COMPILER_DEX_PASS_ME_H_ + +#include +#include "pass.h" + +namespace art { + +// Forward declarations. +struct BasicBlock; +struct CompilationUnit; +class Pass; + +/** + * @brief OptimizationFlag is an enumeration to perform certain tasks for a given pass. + * @details Each enum should be a power of 2 to be correctly used. + */ +enum OptimizationFlag { + kOptimizationBasicBlockChange = 1, /**< @brief Has there been a change to a BasicBlock? */ + kOptimizationDefUsesChange = 2, /**< @brief Has there been a change to a def-use? */ + kLoopStructureChange = 4, /**< @brief Has there been a loop structural change? */ +}; + +// Data holder class. +class PassMEDataHolder: public PassDataHolder { + public: + CompilationUnit* c_unit; + BasicBlock* bb; + void* data; +}; + +enum DataFlowAnalysisMode { + kAllNodes = 0, /**< @brief All nodes. */ + kPreOrderDFSTraversal, /**< @brief Depth-First-Search / Pre-Order. */ + kRepeatingPreOrderDFSTraversal, /**< @brief Depth-First-Search / Repeating Pre-Order. */ + kReversePostOrderDFSTraversal, /**< @brief Depth-First-Search / Reverse Post-Order. */ + kRepeatingPostOrderDFSTraversal, /**< @brief Depth-First-Search / Repeating Post-Order. */ + kRepeatingReversePostOrderDFSTraversal, /**< @brief Depth-First-Search / Repeating Reverse Post-Order. */ + kPostOrderDOMTraversal, /**< @brief Dominator tree / Post-Order. */ + kTopologicalSortTraversal, /**< @brief Topological Order traversal. */ + kRepeatingTopologicalSortTraversal, /**< @brief Repeating Topological Order traversal. */ + kLoopRepeatingTopologicalSortTraversal, /**< @brief Loop-repeating Topological Order traversal. */ + kNoNodes, /**< @brief Skip BasicBlock traversal. */ +}; + +/** + * @class Pass + * @brief Pass is the Pass structure for the optimizations. + * @details The following structure has the different optimization passes that we are going to do. + */ +class PassME: public Pass { + public: + explicit PassME(const char* name, DataFlowAnalysisMode type = kAllNodes, + unsigned int flags = 0u, const char* dump = "") + : Pass(name), traversal_type_(type), flags_(flags), dump_cfg_folder_(dump) { + } + + PassME(const char* name, DataFlowAnalysisMode type, const char* dump) + : Pass(name), traversal_type_(type), flags_(0), dump_cfg_folder_(dump) { + } + + PassME(const char* name, const char* dump) + : Pass(name), traversal_type_(kAllNodes), flags_(0), dump_cfg_folder_(dump) { + } + + ~PassME() { + } + + virtual DataFlowAnalysisMode GetTraversal() const { + return traversal_type_; + } + + const char* GetDumpCFGFolder() const { + return dump_cfg_folder_; + } + + bool GetFlag(OptimizationFlag flag) const { + return (flags_ & flag); + } + + protected: + /** @brief Type of traversal: determines the order to execute the pass on the BasicBlocks. */ + const DataFlowAnalysisMode traversal_type_; + + /** @brief Flags for additional directives: used to determine if a particular post-optimization pass is necessary. */ + const unsigned int flags_; + + /** @brief CFG Dump Folder: what sub-folder to use for dumping the CFGs post pass. */ + const char* const dump_cfg_folder_; +}; +} // namespace art +#endif // ART_COMPILER_DEX_PASS_ME_H_ diff --git a/compiler/dex/portable/mir_to_gbc.cc b/compiler/dex/portable/mir_to_gbc.cc index 90cec750398..fd67608e736 100644 --- a/compiler/dex/portable/mir_to_gbc.cc +++ b/compiler/dex/portable/mir_to_gbc.cc @@ -30,10 +30,11 @@ #include "dex/compiler_internals.h" #include "dex/dataflow_iterator-inl.h" #include "dex/frontend.h" -#include "mir_to_gbc.h" - +#include "llvm/ir_builder.h" #include "llvm/llvm_compilation_unit.h" #include "llvm/utils_llvm.h" +#include "mir_to_gbc.h" +#include "thread-inl.h" const char* kLabelFormat = "%c0x%x_%d"; const char kInvalidBlock = 0xff; @@ -41,6 +42,22 @@ const char kNormalBlock = 'L'; const char kCatchBlock = 'C'; namespace art { +namespace llvm { +::llvm::Module* makeLLVMModuleContents(::llvm::Module* module); +} + +LLVMInfo::LLVMInfo() { + // 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() { +} ::llvm::BasicBlock* MirConverter::GetLLVMBlock(int id) { return id_to_block_map_.Get(id); @@ -132,7 +149,7 @@ void MirConverter::ConvertPackedSwitch(BasicBlock* bb, ::llvm::Value* value = GetLLVMValue(rl_src.orig_sreg); ::llvm::SwitchInst* sw = - irb_->CreateSwitch(value, GetLLVMBlock(bb->fall_through->id), + irb_->CreateSwitch(value, GetLLVMBlock(bb->fall_through), payload->case_count); for (uint16_t i = 0; i < payload->case_count; ++i) { @@ -143,8 +160,8 @@ void MirConverter::ConvertPackedSwitch(BasicBlock* 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; + bb->taken = NullBasicBlockId; + bb->fall_through = NullBasicBlockId; } void MirConverter::ConvertSparseSwitch(BasicBlock* bb, @@ -159,7 +176,7 @@ void MirConverter::ConvertSparseSwitch(BasicBlock* bb, ::llvm::Value* value = GetLLVMValue(rl_src.orig_sreg); ::llvm::SwitchInst* sw = - irb_->CreateSwitch(value, GetLLVMBlock(bb->fall_through->id), + irb_->CreateSwitch(value, GetLLVMBlock(bb->fall_through), payload->case_count); for (size_t i = 0; i < payload->case_count; ++i) { @@ -170,8 +187,8 @@ void MirConverter::ConvertSparseSwitch(BasicBlock* 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; + bb->taken = NullBasicBlockId; + bb->fall_through = NullBasicBlockId; } void MirConverter::ConvertSget(int32_t field_index, @@ -311,22 +328,22 @@ ::llvm::Value* MirConverter::ConvertCompare(ConditionCode cc, void MirConverter::ConvertCompareAndBranch(BasicBlock* bb, MIR* mir, ConditionCode cc, RegLocation rl_src1, RegLocation rl_src2) { - if (bb->taken->start_offset <= mir->offset) { + if (mir_graph_->GetBasicBlock(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)); + irb_->CreateCondBr(cond_value, GetLLVMBlock(bb->taken), + GetLLVMBlock(bb->fall_through)); // Don't redo the fallthrough branch in the BB driver - bb->fall_through = NULL; + bb->fall_through = NullBasicBlockId; } void MirConverter::ConvertCompareZeroAndBranch(BasicBlock* bb, MIR* mir, ConditionCode cc, RegLocation rl_src1) { - if (bb->taken->start_offset <= mir->offset) { + if (mir_graph_->GetBasicBlock(bb->taken)->start_offset <= mir->offset) { EmitSuspendCheck(); } ::llvm::Value* src1 = GetLLVMValue(rl_src1.orig_sreg); @@ -337,10 +354,10 @@ void MirConverter::ConvertCompareZeroAndBranch(BasicBlock* bb, 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)); + irb_->CreateCondBr(cond_value, GetLLVMBlock(bb->taken), + GetLLVMBlock(bb->fall_through)); // Don't redo the fallthrough branch in the BB driver - bb->fall_through = NULL; + bb->fall_through = NullBasicBlockId; } ::llvm::Value* MirConverter::GenDivModOp(bool is_div, bool is_long, @@ -695,7 +712,7 @@ bool MirConverter::ConvertMIRNode(MIR* mir, BasicBlock* bb, int opt_flags = mir->optimization_flags; if (cu_->verbose) { - if (op_val < kMirOpFirst) { + if (!IsPseudoMirOp(op_val)) { 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; @@ -705,7 +722,7 @@ bool MirConverter::ConvertMIRNode(MIR* mir, BasicBlock* bb, /* Prep Src and Dest locations */ int next_sreg = 0; int next_loc = 0; - int attrs = mir_graph_->oat_data_flow_attributes_[opcode]; + uint64_t attrs = MirGraph::GetDataFlowAttributes(opcode); rl_src[0] = rl_src[1] = rl_src[2] = mir_graph_->GetBadLoc(); if (attrs & DF_UA) { if (attrs & DF_A_WIDE) { @@ -941,10 +958,10 @@ bool MirConverter::ConvertMIRNode(MIR* mir, BasicBlock* bb, case Instruction::GOTO: case Instruction::GOTO_16: case Instruction::GOTO_32: { - if (bb->taken->start_offset <= bb->start_offset) { + if (mir_graph_->GetBasicBlock(bb->taken)->start_offset <= bb->start_offset) { EmitSuspendCheck(); } - irb_->CreateBr(GetLLVMBlock(bb->taken->id)); + irb_->CreateBr(GetLLVMBlock(bb->taken)); } break; @@ -1190,11 +1207,11 @@ bool MirConverter::ConvertMIRNode(MIR* mir, BasicBlock* bb, * If it might rethrow, force termination * of the following block. */ - if (bb->fall_through == NULL) { + if (bb->fall_through == NullBasicBlockId) { irb_->CreateUnreachable(); } else { - bb->fall_through->fall_through = NULL; - bb->fall_through->taken = NULL; + mir_graph_->GetBasicBlock(bb->fall_through)->fall_through = NullBasicBlockId; + mir_graph_->GetBasicBlock(bb->fall_through)->taken = NullBasicBlockId; } break; @@ -1522,7 +1539,7 @@ void MirConverter::SetMethodInfo() { 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_->GetNumUsedCompilerTemps())); 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); @@ -1533,7 +1550,7 @@ 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) { + if (!IsPseudoMirOp(opcode)) { // Stop after first non-pseudo MIR op. continue; } @@ -1552,7 +1569,7 @@ void MirConverter::HandlePhiNodes(BasicBlock* bb, ::llvm::BasicBlock* llvm_bb) { if (rl_dest.high_word) { continue; // No Phi node - handled via low word } - int* incoming = reinterpret_cast(mir->dalvikInsn.vB); + BasicBlockId* incoming = mir->meta.phi_incoming; ::llvm::Type* phi_type = LlvmTypeFromLocRec(rl_dest); ::llvm::PHINode* phi = irb_->CreatePHI(phi_type, mir->ssa_rep->num_uses); @@ -1597,8 +1614,8 @@ void MirConverter::ConvertExtendedMIR(BasicBlock* bb, MIR* mir, break; } case kMirOpNop: - if ((mir == bb->last_mir_insn) && (bb->taken == NULL) && - (bb->fall_through == NULL)) { + if ((mir == bb->last_mir_insn) && (bb->taken == NullBasicBlockId) && + (bb->fall_through == NullBasicBlockId)) { irb_->CreateUnreachable(); } break; @@ -1660,7 +1677,6 @@ bool MirConverter::BlockBitcodeConversion(BasicBlock* bb) { 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); @@ -1718,25 +1734,23 @@ bool MirConverter::BlockBitcodeConversion(BasicBlock* bb) { 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) { + if (bb->successor_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); + GrowableArray::Iterator iter(bb->successor_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()); + irb_->CreateSwitch(switch_key, work_bb, bb->successor_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); + GetLLVMBlock(successor_block_info->block); int type_index = successor_block_info->key; sw->addCase(irb_->getInt32(type_index), target); } @@ -1745,7 +1759,7 @@ bool MirConverter::BlockBitcodeConversion(BasicBlock* bb) { } } - if (opcode >= kMirOpFirst) { + if (IsPseudoMirOp(opcode)) { ConvertExtendedMIR(bb, mir, llvm_bb); continue; } @@ -1761,9 +1775,9 @@ bool MirConverter::BlockBitcodeConversion(BasicBlock* bb) { } 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)); + entry_target_bb_ = GetLLVMBlock(bb->fall_through); + } else if ((bb->fall_through != NullBasicBlockId) && !bb->terminated_by_return) { + irb_->CreateBr(GetLLVMBlock(bb->fall_through)); } return false; @@ -1877,7 +1891,7 @@ void MirConverter::MethodMIR2Bitcode() { CreateFunction(); // Create an LLVM basic block for each MIR block in dfs preorder - PreOrderDfsIterator iter(mir_graph_, false /* not iterative */); + PreOrderDfsIterator iter(mir_graph_); for (BasicBlock* bb = iter.Next(); bb != NULL; bb = iter.Next()) { CreateLLVMBasicBlock(bb); } @@ -1909,7 +1923,7 @@ void MirConverter::MethodMIR2Bitcode() { } } - PreOrderDfsIterator iter2(mir_graph_, false /* not iterative */); + PreOrderDfsIterator iter2(mir_graph_); for (BasicBlock* bb = iter2.Next(); bb != NULL; bb = iter2.Next()) { BlockBitcodeConversion(bb); } diff --git a/compiler/dex/portable/mir_to_gbc.h b/compiler/dex/portable/mir_to_gbc.h index 2b681f60971..e97634c5192 100644 --- a/compiler/dex/portable/mir_to_gbc.h +++ b/compiler/dex/portable/mir_to_gbc.h @@ -17,11 +17,18 @@ #ifndef ART_COMPILER_DEX_PORTABLE_MIR_TO_GBC_H_ #define ART_COMPILER_DEX_PORTABLE_MIR_TO_GBC_H_ +#include +#include +#include +#include +#include + #include "invoke_type.h" #include "compiled_method.h" #include "dex/compiler_enums.h" #include "dex/compiler_ir.h" #include "dex/backend.h" +#include "llvm/intrinsic_helper.h" #include "llvm/llvm_compilation_unit.h" #include "safe_map.h" diff --git a/compiler/dex/post_opt_passes.cc b/compiler/dex/post_opt_passes.cc new file mode 100644 index 00000000000..13716529848 --- /dev/null +++ b/compiler/dex/post_opt_passes.cc @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "post_opt_passes.h" +#include "dataflow_iterator.h" +#include "dataflow_iterator-inl.h" + +namespace art { + +/* + * MethodUseCount pass implementation start. + */ +bool MethodUseCount::Gate(const PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + // First initialize the data. + c_unit->mir_graph->InitializeMethodUses(); + + // Now check if the pass is to be ignored. + bool res = ((c_unit->disable_opt & (1 << kPromoteRegs)) == 0); + + return res; +} + +bool MethodUseCount::Worker(const PassDataHolder* data) const { + DCHECK(data != nullptr); + const PassMEDataHolder* pass_me_data_holder = down_cast(data); + CompilationUnit* c_unit = pass_me_data_holder->c_unit; + DCHECK(c_unit != nullptr); + BasicBlock* bb = pass_me_data_holder->bb; + DCHECK(bb != nullptr); + c_unit->mir_graph->CountUses(bb); + // No need of repeating, so just return false. + return false; +} + + +bool ClearPhiInstructions::Worker(const PassDataHolder* data) const { + DCHECK(data != nullptr); + const PassMEDataHolder* pass_me_data_holder = down_cast(data); + CompilationUnit* c_unit = pass_me_data_holder->c_unit; + DCHECK(c_unit != nullptr); + BasicBlock* bb = pass_me_data_holder->bb; + DCHECK(bb != nullptr); + MIR* mir = bb->first_mir_insn; + + while (mir != nullptr) { + MIR* next = mir->next; + + Instruction::Code opcode = mir->dalvikInsn.opcode; + + if (opcode == static_cast (kMirOpPhi)) { + bb->RemoveMIR(mir); + } + + mir = next; + } + + // We do not care in reporting a change or not in the MIR. + return false; +} + +void CalculatePredecessors::Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + // First get the MIRGraph here to factorize a bit the code. + MIRGraph *mir_graph = c_unit->mir_graph.get(); + + // First clear all predecessors. + AllNodesIterator first(mir_graph); + for (BasicBlock* bb = first.Next(); bb != nullptr; bb = first.Next()) { + bb->predecessors->Reset(); + } + + // Now calculate all predecessors. + AllNodesIterator second(mir_graph); + for (BasicBlock* bb = second.Next(); bb != nullptr; bb = second.Next()) { + // We only care about non hidden blocks. + if (bb->hidden == true) { + continue; + } + + // Create iterator for visiting children. + ChildBlockIterator child_iter(bb, mir_graph); + + // Now iterate through the children to set the predecessor bits. + for (BasicBlock* child = child_iter.Next(); child != nullptr; child = child_iter.Next()) { + child->predecessors->Insert(bb->id); + } + } +} + +} // namespace art diff --git a/compiler/dex/post_opt_passes.h b/compiler/dex/post_opt_passes.h new file mode 100644 index 00000000000..a1b0df4b328 --- /dev/null +++ b/compiler/dex/post_opt_passes.h @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_DEX_POST_OPT_PASSES_H_ +#define ART_COMPILER_DEX_POST_OPT_PASSES_H_ + +#include "compiler_internals.h" +#include "pass_me.h" + +namespace art { + +/** + * @class InitializeData + * @brief There is some data that needs to be initialized before performing + * the post optimization passes. + */ +class InitializeData : public PassME { + public: + InitializeData() : PassME("InitializeData") { + } + + void Start(PassDataHolder* data) const { + // New blocks may have been inserted so the first thing we do is ensure that + // the c_unit's number of blocks matches the actual count of basic blocks. + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + c_unit->mir_graph.get()->InitializeBasicBlockData(); + c_unit->mir_graph.get()->SSATransformationStart(); + } +}; + +/** + * @class MethodUseCount + * @brief Count the register uses of the method + */ +class MethodUseCount : public PassME { + public: + MethodUseCount() : PassME("UseCount") { + } + + bool Worker(const PassDataHolder* data) const; + + bool Gate(const PassDataHolder* data) const; +}; + +/** + * @class ClearPhiInformation + * @brief Clear the PHI nodes from the CFG. + */ +class ClearPhiInstructions : public PassME { + public: + ClearPhiInstructions() : PassME("ClearPhiInstructions") { + } + + bool Worker(const PassDataHolder* data) const; +}; + +/** + * @class CalculatePredecessors + * @brief Calculate the predecessor BitVector of each Basicblock. + */ +class CalculatePredecessors : public PassME { + public: + CalculatePredecessors() : PassME("CalculatePredecessors") { + } + + void Start(PassDataHolder* data) const; +}; + +/** + * @class DFSOrders + * @brief Compute the DFS order of the MIR graph + */ +class DFSOrders : public PassME { + public: + DFSOrders() : PassME("DFSOrders") { + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + c_unit->mir_graph.get()->ComputeDFSOrders(); + } +}; + +/** + * @class BuildDomination + * @brief Build the domination information of the MIR Graph + */ +class BuildDomination : public PassME { + public: + BuildDomination() : PassME("BuildDomination") { + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + c_unit->mir_graph.get()->ComputeDominators(); + c_unit->mir_graph.get()->CompilerInitializeSSAConversion(); + } + + void End(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + // Verify the dataflow information after the pass. + if (c_unit->enable_debug & (1 << kDebugVerifyDataflow)) { + c_unit->mir_graph->VerifyDataflow(); + } + } +}; + +/** + * @class TopologicalSortOrders + * @brief Compute the topological sort order of the MIR graph + */ +class TopologicalSortOrders : public PassME { + public: + TopologicalSortOrders() : PassME("TopologicalSortOrders") { + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + c_unit->mir_graph.get()->ComputeTopologicalSortOrder(); + } +}; + +/** + * @class DefBlockMatrix + * @brief Calculate the matrix of definition per basic block + */ +class DefBlockMatrix : public PassME { + public: + DefBlockMatrix() : PassME("DefBlockMatrix") { + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + c_unit->mir_graph.get()->ComputeDefBlockMatrix(); + } +}; + +/** + * @class CreatePhiNodes + * @brief Pass to create the phi nodes after SSA calculation + */ +class CreatePhiNodes : public PassME { + public: + CreatePhiNodes() : PassME("CreatePhiNodes") { + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + c_unit->mir_graph.get()->InsertPhiNodes(); + } +}; + +/** + * @class ClearVisitedFlag + * @brief Pass to clear the visited flag for all basic blocks. + */ + +class ClearVisitedFlag : public PassME { + public: + ClearVisitedFlag() : PassME("ClearVisitedFlag") { + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + c_unit->mir_graph.get()->ClearAllVisitedFlags(); + } +}; + +/** + * @class SSAConversion + * @brief Pass for SSA conversion of MIRs + */ +class SSAConversion : public PassME { + public: + SSAConversion() : PassME("SSAConversion") { + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + MIRGraph *mir_graph = c_unit->mir_graph.get(); + mir_graph->DoDFSPreOrderSSARename(mir_graph->GetEntryBlock()); + } +}; + +/** + * @class PhiNodeOperands + * @brief Pass to insert the Phi node operands to basic blocks + */ +class PhiNodeOperands : public PassME { + public: + PhiNodeOperands() : PassME("PhiNodeOperands", kPreOrderDFSTraversal) { + } + + bool Worker(const PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + BasicBlock* bb = down_cast(data)->bb; + DCHECK(bb != nullptr); + c_unit->mir_graph->InsertPhiNodeOperands(bb); + // No need of repeating, so just return false. + return false; + } +}; + +/** + * @class InitRegLocations + * @brief Initialize Register Locations. + */ +class PerformInitRegLocations : public PassME { + public: + PerformInitRegLocations() : PassME("PerformInitRegLocation") { + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + c_unit->mir_graph->InitRegLocations(); + } +}; + +/** + * @class ConstantPropagation + * @brief Perform a constant propagation pass. + */ +class ConstantPropagation : public PassME { + public: + ConstantPropagation() : PassME("ConstantPropagation") { + } + + bool Worker(const PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + BasicBlock* bb = down_cast(data)->bb; + DCHECK(bb != nullptr); + c_unit->mir_graph->DoConstantPropagation(bb); + // No need of repeating, so just return false. + return false; + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + c_unit->mir_graph->InitializeConstantPropagation(); + } +}; + +/** + * @class FreeData + * @brief There is some data that needs to be freed after performing the post optimization passes. + */ +class FreeData : public PassME { + public: + FreeData() : PassME("FreeData") { + } + + void End(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast(data)->c_unit; + DCHECK(c_unit != nullptr); + c_unit->mir_graph.get()->SSATransformationEnd(); + } +}; + +} // namespace art + +#endif // ART_COMPILER_DEX_POST_OPT_PASSES_H_ diff --git a/compiler/dex/quick/arm/arm_lir.h b/compiler/dex/quick/arm/arm_lir.h index 2f54190ae7e..a54e8b7e33a 100644 --- a/compiler/dex/quick/arm/arm_lir.h +++ b/compiler/dex/quick/arm/arm_lir.h @@ -29,7 +29,8 @@ namespace art { * 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) + * r4 : If ARM_R4_SUSPEND_FLAG is set then reserved as a suspend check/debugger + * assist flag, otherwise a callee save promotion target. * r5 : Callee save (promotion target) * r6 : Callee save (promotion target) * r7 : Callee save (promotion target) @@ -93,37 +94,10 @@ namespace art { * +========================+ */ -// 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 +// Flag for using R4 to do suspend check +#define ARM_R4_SUSPEND_FLAG enum ArmResourceEncodingPos { kArmGPReg0 = 0, @@ -135,95 +109,207 @@ enum ArmResourceEncodingPos { 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, + r0 = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 0, + r1 = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 1, + r2 = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 2, + r3 = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 3, +#ifdef ARM_R4_SUSPEND_FLAG + rARM_SUSPEND = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 4, +#else + r4 = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 4, +#endif + r5 = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 5, + r6 = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 6, + r7 = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 7, + r8 = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 8, + rARM_SELF = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 9, + r10 = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 10, + r11 = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 11, + r12 = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 12, + r13sp = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 13, + rARM_SP = r13sp, + r14lr = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 14, + rARM_LR = r14lr, + r15pc = RegStorage::k32BitSolo | RegStorage::kCoreRegister | 15, + rARM_PC = r15pc, + + fr0 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 0, + fr1 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 1, + fr2 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 2, + fr3 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 3, + fr4 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 4, + fr5 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 5, + fr6 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 6, + fr7 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 7, + fr8 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 8, + fr9 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 9, + fr10 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 10, + fr11 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 11, + fr12 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 12, + fr13 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 13, + fr14 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 14, + fr15 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 15, + fr16 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 16, + fr17 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 17, + fr18 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 18, + fr19 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 19, + fr20 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 20, + fr21 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 21, + fr22 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 22, + fr23 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 23, + fr24 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 24, + fr25 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 25, + fr26 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 26, + fr27 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 27, + fr28 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 28, + fr29 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 29, + fr30 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 30, + fr31 = RegStorage::k32BitSolo | RegStorage::kFloatingPoint | 31, + + dr0 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 0, + dr1 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 1, + dr2 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 2, + dr3 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 3, + dr4 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 4, + dr5 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 5, + dr6 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 6, + dr7 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 7, + dr8 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 8, + dr9 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 9, + dr10 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 10, + dr11 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 11, + dr12 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 12, + dr13 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 13, + dr14 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 14, + dr15 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 15, +#if 0 + // Enable when def/use and runtime able to handle these. + dr16 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 16, + dr17 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 17, + dr18 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 18, + dr19 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 19, + dr20 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 20, + dr21 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 21, + dr22 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 22, + dr23 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 23, + dr24 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 24, + dr25 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 25, + dr26 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 26, + dr27 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 27, + dr28 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 28, + dr29 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 29, + dr30 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 30, + dr31 = RegStorage::k64BitSolo | RegStorage::kFloatingPoint | 31, +#endif }; -// 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 +constexpr RegStorage rs_r0(RegStorage::kValid | r0); +constexpr RegStorage rs_r1(RegStorage::kValid | r1); +constexpr RegStorage rs_r2(RegStorage::kValid | r2); +constexpr RegStorage rs_r3(RegStorage::kValid | r3); +#ifdef ARM_R4_SUSPEND_FLAG +constexpr RegStorage rs_rARM_SUSPEND(RegStorage::kValid | rARM_SUSPEND); +#else +constexpr RegStorage rs_r4(RegStorage::kValid | r4); +#endif +constexpr RegStorage rs_r5(RegStorage::kValid | r5); +constexpr RegStorage rs_r6(RegStorage::kValid | r6); +constexpr RegStorage rs_r7(RegStorage::kValid | r7); +constexpr RegStorage rs_r8(RegStorage::kValid | r8); +constexpr RegStorage rs_rARM_SELF(RegStorage::kValid | rARM_SELF); +constexpr RegStorage rs_r10(RegStorage::kValid | r10); +constexpr RegStorage rs_r11(RegStorage::kValid | r11); +constexpr RegStorage rs_r12(RegStorage::kValid | r12); +constexpr RegStorage rs_r13sp(RegStorage::kValid | r13sp); +constexpr RegStorage rs_rARM_SP(RegStorage::kValid | rARM_SP); +constexpr RegStorage rs_r14lr(RegStorage::kValid | r14lr); +constexpr RegStorage rs_rARM_LR(RegStorage::kValid | rARM_LR); +constexpr RegStorage rs_r15pc(RegStorage::kValid | r15pc); +constexpr RegStorage rs_rARM_PC(RegStorage::kValid | rARM_PC); +constexpr RegStorage rs_invalid(RegStorage::kInvalid); + +constexpr RegStorage rs_fr0(RegStorage::kValid | fr0); +constexpr RegStorage rs_fr1(RegStorage::kValid | fr1); +constexpr RegStorage rs_fr2(RegStorage::kValid | fr2); +constexpr RegStorage rs_fr3(RegStorage::kValid | fr3); +constexpr RegStorage rs_fr4(RegStorage::kValid | fr4); +constexpr RegStorage rs_fr5(RegStorage::kValid | fr5); +constexpr RegStorage rs_fr6(RegStorage::kValid | fr6); +constexpr RegStorage rs_fr7(RegStorage::kValid | fr7); +constexpr RegStorage rs_fr8(RegStorage::kValid | fr8); +constexpr RegStorage rs_fr9(RegStorage::kValid | fr9); +constexpr RegStorage rs_fr10(RegStorage::kValid | fr10); +constexpr RegStorage rs_fr11(RegStorage::kValid | fr11); +constexpr RegStorage rs_fr12(RegStorage::kValid | fr12); +constexpr RegStorage rs_fr13(RegStorage::kValid | fr13); +constexpr RegStorage rs_fr14(RegStorage::kValid | fr14); +constexpr RegStorage rs_fr15(RegStorage::kValid | fr15); +constexpr RegStorage rs_fr16(RegStorage::kValid | fr16); +constexpr RegStorage rs_fr17(RegStorage::kValid | fr17); +constexpr RegStorage rs_fr18(RegStorage::kValid | fr18); +constexpr RegStorage rs_fr19(RegStorage::kValid | fr19); +constexpr RegStorage rs_fr20(RegStorage::kValid | fr20); +constexpr RegStorage rs_fr21(RegStorage::kValid | fr21); +constexpr RegStorage rs_fr22(RegStorage::kValid | fr22); +constexpr RegStorage rs_fr23(RegStorage::kValid | fr23); +constexpr RegStorage rs_fr24(RegStorage::kValid | fr24); +constexpr RegStorage rs_fr25(RegStorage::kValid | fr25); +constexpr RegStorage rs_fr26(RegStorage::kValid | fr26); +constexpr RegStorage rs_fr27(RegStorage::kValid | fr27); +constexpr RegStorage rs_fr28(RegStorage::kValid | fr28); +constexpr RegStorage rs_fr29(RegStorage::kValid | fr29); +constexpr RegStorage rs_fr30(RegStorage::kValid | fr30); +constexpr RegStorage rs_fr31(RegStorage::kValid | fr31); + +constexpr RegStorage rs_dr0(RegStorage::kValid | dr0); +constexpr RegStorage rs_dr1(RegStorage::kValid | dr1); +constexpr RegStorage rs_dr2(RegStorage::kValid | dr2); +constexpr RegStorage rs_dr3(RegStorage::kValid | dr3); +constexpr RegStorage rs_dr4(RegStorage::kValid | dr4); +constexpr RegStorage rs_dr5(RegStorage::kValid | dr5); +constexpr RegStorage rs_dr6(RegStorage::kValid | dr6); +constexpr RegStorage rs_dr7(RegStorage::kValid | dr7); +constexpr RegStorage rs_dr8(RegStorage::kValid | dr8); +constexpr RegStorage rs_dr9(RegStorage::kValid | dr9); +constexpr RegStorage rs_dr10(RegStorage::kValid | dr10); +constexpr RegStorage rs_dr11(RegStorage::kValid | dr11); +constexpr RegStorage rs_dr12(RegStorage::kValid | dr12); +constexpr RegStorage rs_dr13(RegStorage::kValid | dr13); +constexpr RegStorage rs_dr14(RegStorage::kValid | dr14); +constexpr RegStorage rs_dr15(RegStorage::kValid | dr15); +#if 0 +constexpr RegStorage rs_dr16(RegStorage::kValid | dr16); +constexpr RegStorage rs_dr17(RegStorage::kValid | dr17); +constexpr RegStorage rs_dr18(RegStorage::kValid | dr18); +constexpr RegStorage rs_dr19(RegStorage::kValid | dr19); +constexpr RegStorage rs_dr20(RegStorage::kValid | dr20); +constexpr RegStorage rs_dr21(RegStorage::kValid | dr21); +constexpr RegStorage rs_dr22(RegStorage::kValid | dr22); +constexpr RegStorage rs_dr23(RegStorage::kValid | dr23); +constexpr RegStorage rs_dr24(RegStorage::kValid | dr24); +constexpr RegStorage rs_dr25(RegStorage::kValid | dr25); +constexpr RegStorage rs_dr26(RegStorage::kValid | dr26); +constexpr RegStorage rs_dr27(RegStorage::kValid | dr27); +constexpr RegStorage rs_dr28(RegStorage::kValid | dr28); +constexpr RegStorage rs_dr29(RegStorage::kValid | dr29); +constexpr RegStorage rs_dr30(RegStorage::kValid | dr30); +constexpr RegStorage rs_dr31(RegStorage::kValid | dr31); +#endif + +// RegisterLocation templates return values (r0, or r0/r1). +const RegLocation arm_loc_c_return + {kLocPhysReg, 0, 0, 0, 0, 0, 0, 0, 1, + RegStorage(RegStorage::k32BitSolo, r0), INVALID_SREG, INVALID_SREG}; +const RegLocation arm_loc_c_return_wide + {kLocPhysReg, 1, 0, 0, 0, 0, 0, 0, 1, + RegStorage(RegStorage::k64BitPair, r0, r1), INVALID_SREG, INVALID_SREG}; +const RegLocation arm_loc_c_return_float + {kLocPhysReg, 0, 0, 0, 0, 0, 0, 0, 1, + RegStorage(RegStorage::k32BitSolo, r0), INVALID_SREG, INVALID_SREG}; +const RegLocation arm_loc_c_return_double + {kLocPhysReg, 1, 0, 0, 0, 0, 0, 0, 1, + RegStorage(RegStorage::k64BitPair, r0, r1), INVALID_SREG, INVALID_SREG}; enum ArmShiftEncodings { kArmLsl = 0x0, @@ -241,7 +327,7 @@ 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]*/ + 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]. @@ -296,6 +382,8 @@ enum ArmOpcode { 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]. + kThumbRev, // rev [1011101000] rm[5..3] rd[2..0] + kThumbRevsh, // revsh [1011101011] rm[5..3] rd[2..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]. @@ -324,34 +412,36 @@ enum ArmOpcode { 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]. + kThumb2VmlaF64, // vmla.F64 vd, vn, vm [111011100000] vn[19..16] vd[15..12] [10110000] vm[3..0]. + kThumb2VcvtIF, // vcvt.F32.S32 vd, vm [1110111010111000] vd[15..12] [10101100] 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]. + kThumb2VcvtF64S32, // vcvt.F64.S32 vd, vm [1110111010111000] vd[15..12] [10111100] vm[3..0]. + kThumb2VcvtF64U32, // vcvt.F64.U32 vd, vm [1110111010111000] vd[15..12] [10110100] 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. + kThumb2MovI8M, // 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]*/ + 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,