Merge "Avoid read barriers for inlined check cast"
diff --git a/build/Android.common_test.mk b/build/Android.common_test.mk
index d2e3371..291db8b 100644
--- a/build/Android.common_test.mk
+++ b/build/Android.common_test.mk
@@ -124,12 +124,17 @@
ART_TEST_RUN_TEST_MULTI_IMAGE ?= $(ART_TEST_FULL)
# Define the command run on test failure. $(1) is the name of the test. Executed by the shell.
+# If the test was a top-level make target (e.g. `test-art-host-gtest-codegen_test64`), the command
+# fails with exit status 1 (returned by the last `grep` statement below).
+# Otherwise (e.g., if the test was run as a prerequisite of a compound test command, such as
+# `test-art-host-gtest-codegen_test`), the command does not fail, as this would break rules running
+# ART_TEST_PREREQ_FINISHED as one of their actions, which expects *all* prerequisites *not* to fail.
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 )))
+ || (echo -e "$(1) \e[91mFAILED\e[0m" >&2; echo $(MAKECMDGOALS) | grep -q -v $(1))))
endef
ifeq ($(ART_TEST_QUIET),true)
diff --git a/compiler/Android.bp b/compiler/Android.bp
index f1bf27e..6edb639 100644
--- a/compiler/Android.bp
+++ b/compiler/Android.bp
@@ -42,6 +42,7 @@
"linker/vector_output_stream.cc",
"linker/relative_patcher.cc",
"jit/jit_compiler.cc",
+ "jit/jit_logger.cc",
"jni/quick/calling_convention.cc",
"jni/quick/jni_compiler.cc",
"optimizing/block_builder.cc",
@@ -418,6 +419,7 @@
},
mips: {
srcs: [
+ "optimizing/emit_swap_mips_test.cc",
"utils/mips/assembler_mips_test.cc",
"utils/mips/assembler_mips32r6_test.cc",
],
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc
index f83d37c..9dfb434 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -171,19 +171,10 @@
size_t thread_count = compiler_driver_->GetThreadCount();
if (compiler_options_->GetGenerateDebugInfo()) {
-#ifdef ART_TARGET_ANDROID
- const char* prefix = "/data/misc/trace";
-#else
- const char* prefix = "/tmp";
-#endif
DCHECK_EQ(thread_count, 1u)
<< "Generating debug info only works with one compiler thread";
- std::string perf_filename = std::string(prefix) + "/perf-" + std::to_string(getpid()) + ".map";
- perf_file_.reset(OS::CreateEmptyFileWriteOnly(perf_filename.c_str()));
- if (perf_file_ == nullptr) {
- LOG(ERROR) << "Could not create perf file at " << perf_filename <<
- " Are you on a user build? Perf only works on userdebug/eng builds";
- }
+ jit_logger_.reset(new JitLogger());
+ jit_logger_->OpenLog();
}
size_t inline_depth_limit = compiler_driver_->GetCompilerOptions().GetInlineDepthLimit();
@@ -192,9 +183,8 @@
}
JitCompiler::~JitCompiler() {
- if (perf_file_ != nullptr) {
- UNUSED(perf_file_->Flush());
- UNUSED(perf_file_->Close());
+ if (compiler_options_->GetGenerateDebugInfo()) {
+ jit_logger_->CloseLog();
}
}
@@ -218,19 +208,8 @@
TimingLogger::ScopedTiming t2("Compiling", &logger);
JitCodeCache* const code_cache = runtime->GetJit()->GetCodeCache();
success = compiler_driver_->GetCompiler()->JitCompile(self, code_cache, method, osr);
- if (success && (perf_file_ != nullptr)) {
- const void* ptr = method->GetEntryPointFromQuickCompiledCode();
- std::ostringstream stream;
- stream << std::hex
- << reinterpret_cast<uintptr_t>(ptr)
- << " "
- << code_cache->GetMemorySizeOfCodePointer(ptr)
- << " "
- << method->PrettyMethod()
- << std::endl;
- std::string str = stream.str();
- bool res = perf_file_->WriteFully(str.c_str(), str.size());
- CHECK(res);
+ if (success && (jit_logger_ != nullptr)) {
+ jit_logger_->WriteLog(code_cache, method);
}
}
diff --git a/compiler/jit/jit_compiler.h b/compiler/jit/jit_compiler.h
index ea2747c..f0f24d3 100644
--- a/compiler/jit/jit_compiler.h
+++ b/compiler/jit/jit_compiler.h
@@ -19,6 +19,7 @@
#include "base/mutex.h"
#include "compiled_method.h"
+#include "jit_logger.h"
#include "driver/compiler_driver.h"
#include "driver/compiler_options.h"
@@ -50,7 +51,7 @@
std::unique_ptr<CumulativeLogger> cumulative_logger_;
std::unique_ptr<CompilerDriver> compiler_driver_;
std::unique_ptr<const InstructionSetFeatures> instruction_set_features_;
- std::unique_ptr<File> perf_file_;
+ std::unique_ptr<JitLogger> jit_logger_;
JitCompiler();
diff --git a/compiler/jit/jit_logger.cc b/compiler/jit/jit_logger.cc
new file mode 100644
index 0000000..9ce3b0c
--- /dev/null
+++ b/compiler/jit/jit_logger.cc
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jit_logger.h"
+
+#include "arch/instruction_set.h"
+#include "art_method-inl.h"
+#include "base/time_utils.h"
+#include "base/unix_file/fd_file.h"
+#include "driver/compiler_driver.h"
+#include "jit/jit.h"
+#include "jit/jit_code_cache.h"
+
+namespace art {
+namespace jit {
+
+#ifdef ART_TARGET_ANDROID
+static const char* kLogPrefix = "/data/misc/trace";
+#else
+static const char* kLogPrefix = "/tmp";
+#endif
+
+// File format of perf-PID.map:
+// +---------------------+
+// |ADDR SIZE symbolname1|
+// |ADDR SIZE symbolname2|
+// |... |
+// +---------------------+
+void JitLogger::OpenPerfMapLog() {
+ std::string pid_str = std::to_string(getpid());
+ std::string perf_filename = std::string(kLogPrefix) + "/perf-" + pid_str + ".map";
+ perf_file_.reset(OS::CreateEmptyFileWriteOnly(perf_filename.c_str()));
+ if (perf_file_ == nullptr) {
+ LOG(ERROR) << "Could not create perf file at " << perf_filename <<
+ " Are you on a user build? Perf only works on userdebug/eng builds";
+ }
+}
+
+void JitLogger::WritePerfMapLog(JitCodeCache* code_cache, ArtMethod* method) {
+ if (perf_file_ != nullptr) {
+ const void* ptr = method->GetEntryPointFromQuickCompiledCode();
+ size_t code_size = code_cache->GetMemorySizeOfCodePointer(ptr);
+ std::string method_name = method->PrettyMethod();
+
+ std::ostringstream stream;
+ stream << std::hex
+ << reinterpret_cast<uintptr_t>(ptr)
+ << " "
+ << code_size
+ << " "
+ << method_name
+ << std::endl;
+ std::string str = stream.str();
+ bool res = perf_file_->WriteFully(str.c_str(), str.size());
+ if (!res) {
+ LOG(WARNING) << "Failed to write jitted method info in log: write failure.";
+ }
+ } else {
+ LOG(WARNING) << "Failed to write jitted method info in log: log file doesn't exist.";
+ }
+}
+
+void JitLogger::ClosePerfMapLog() {
+ if (perf_file_ != nullptr) {
+ UNUSED(perf_file_->Flush());
+ UNUSED(perf_file_->Close());
+ }
+}
+
+// File format of jit-PID.jump:
+//
+// +--------------------------------+
+// | PerfJitHeader |
+// +--------------------------------+
+// | PerfJitCodeLoad { | .
+// | struct PerfJitBase; | .
+// | uint32_t process_id_; | .
+// | uint32_t thread_id_; | .
+// | uint64_t vma_; | .
+// | uint64_t code_address_; | .
+// | uint64_t code_size_; | .
+// | uint64_t code_id_; | .
+// | } | .
+// +- -+ .
+// | method_name'\0' | +--> one jitted method
+// +- -+ .
+// | jitted code binary | .
+// | ... | .
+// +--------------------------------+ .
+// | PerfJitCodeDebugInfo { | .
+// | struct PerfJitBase; | .
+// | uint64_t address_; | .
+// | uint64_t entry_count_; | .
+// | struct PerfJitDebugEntry; | .
+// | } | .
+// +--------------------------------+
+// | PerfJitCodeLoad |
+// ...
+//
+struct PerfJitHeader {
+ uint32_t magic_; // Characters "JiTD"
+ uint32_t version_; // Header version
+ uint32_t size_; // Total size of header
+ uint32_t elf_mach_target_; // Elf mach target
+ uint32_t reserved_; // Reserved, currently not used
+ uint32_t process_id_; // Process ID of the JIT compiler
+ uint64_t time_stamp_; // Timestamp when the header is generated
+ uint64_t flags_; // Currently the flags are only used for choosing clock for timestamp,
+ // we set it to 0 to tell perf that we use CLOCK_MONOTONIC clock.
+ static const uint32_t kMagic = 0x4A695444; // "JiTD"
+ static const uint32_t kVersion = 1;
+};
+
+// Each record starts with such basic information: event type, total size, and timestamp.
+struct PerfJitBase {
+ enum PerfJitEvent {
+ // A jitted code load event.
+ // In ART JIT, it is used to log a new method is jit compiled and committed to jit-code-cache.
+ // Note that such kLoad event supports code cache GC in ART JIT.
+ // For every kLoad event recorded in jit-PID.dump and every perf sample recorded in perf.data,
+ // each event/sample has time stamp. In case code cache GC happens in ART JIT, and a new
+ // jitted method is committed to the same address of a previously deleted method,
+ // the time stamp information can help profiler to tell whether this sample belongs to the
+ // era of the first jitted method, or does it belong to the period of the second jitted method.
+ // JitCodeCache doesn't have to record any event on 'code delete'.
+ kLoad = 0,
+
+ // A jitted code move event, i,e. a jitted code moved from one address to another address.
+ // It helps profiler to map samples to the right symbol even when the code is moved.
+ // In ART JIT, this event can help log such behavior:
+ // A jitted method is recorded in previous kLoad event, but due to some reason,
+ // it is moved to another address in jit-code-cache.
+ kMove = 1,
+
+ // Logs debug line/column information.
+ kDebugInfo = 2,
+
+ // Logs JIT VM end of life event.
+ kClose = 3
+ };
+ uint32_t event_; // Must be one of the events defined in PerfJitEvent.
+ uint32_t size_; // Total size of this event record.
+ // For example, for kLoad event, size of the event record is:
+ // sizeof(PerfJitCodeLoad) + method_name.size() + compiled code size.
+ uint64_t time_stamp_; // Timestamp for the event.
+};
+
+// Logs a jitted code load event (kLoad).
+// In ART JIT, it is used to log a new method is jit compiled and commited to jit-code-cache.
+struct PerfJitCodeLoad : PerfJitBase {
+ uint32_t process_id_; // Process ID who performs the jit code load.
+ // In ART JIT, it is the pid of the JIT compiler.
+ uint32_t thread_id_; // Thread ID who performs the jit code load.
+ // In ART JIT, it is the tid of the JIT compiler.
+ uint64_t vma_; // Address of the code section. In ART JIT, because code_address_
+ // uses absolute address, this field is 0.
+ uint64_t code_address_; // Address where is jitted code is loaded.
+ uint64_t code_size_; // Size of the jitted code.
+ uint64_t code_id_; // Unique ID for each jitted code.
+};
+
+// This structure is for source line/column mapping.
+// Currently this feature is not implemented in ART JIT yet.
+struct PerfJitDebugEntry {
+ uint64_t address_; // Code address which maps to the line/column in source.
+ uint32_t line_number_; // Source line number starting at 1.
+ uint32_t column_; // Column discriminator, default 0.
+ const char name_[0]; // Followed by null-terminated name or \0xff\0 if same as previous.
+};
+
+// Logs debug line information (kDebugInfo).
+// This structure is for source line/column mapping.
+// Currently this feature is not implemented in ART JIT yet.
+struct PerfJitCodeDebugInfo : PerfJitBase {
+ uint64_t address_; // Starting code address which the debug info describes.
+ uint64_t entry_count_; // How many instances of PerfJitDebugEntry.
+ PerfJitDebugEntry entries_[0]; // Followed by entry_count_ instances of PerfJitDebugEntry.
+};
+
+static uint32_t GetElfMach() {
+#if defined(__arm__)
+ static const uint32_t kElfMachARM = 0x28;
+ return kElfMachARM;
+#elif defined(__aarch64__)
+ static const uint32_t kElfMachARM64 = 0xB7;
+ return kElfMachARM64;
+#elif defined(__i386__)
+ static const uint32_t kElfMachIA32 = 0x3;
+ return kElfMachIA32;
+#elif defined(__x86_64__)
+ static const uint32_t kElfMachX64 = 0x3E;
+ return kElfMachX64;
+#else
+ UNIMPLEMENTED(WARNING) << "Unsupported architecture in JitLogger";
+ return 0;
+#endif
+}
+
+void JitLogger::OpenMarkerFile() {
+ int fd = jit_dump_file_->Fd();
+ // The 'perf inject' tool requires that the jit-PID.dump file
+ // must have a mmap(PROT_READ|PROT_EXEC) record in perf.data.
+ marker_address_ = mmap(nullptr, kPageSize, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);
+ if (marker_address_ == MAP_FAILED) {
+ LOG(WARNING) << "Failed to create record in perf.data. JITed code profiling will not work.";
+ return;
+ }
+}
+
+void JitLogger::CloseMarkerFile() {
+ if (marker_address_ != nullptr) {
+ munmap(marker_address_, kPageSize);
+ }
+}
+
+void JitLogger::WriteJitDumpDebugInfo() {
+ // In the future, we can add java source file line/column mapping here.
+}
+
+void JitLogger::WriteJitDumpHeader() {
+ PerfJitHeader header;
+
+ std::memset(&header, 0, sizeof(header));
+ header.magic_ = PerfJitHeader::kMagic;
+ header.version_ = PerfJitHeader::kVersion;
+ header.size_ = sizeof(header);
+ header.elf_mach_target_ = GetElfMach();
+ header.process_id_ = static_cast<uint32_t>(getpid());
+ header.time_stamp_ = art::NanoTime(); // CLOCK_MONOTONIC clock is required.
+ header.flags_ = 0;
+
+ bool res = jit_dump_file_->WriteFully(reinterpret_cast<const char*>(&header), sizeof(header));
+ if (!res) {
+ LOG(WARNING) << "Failed to write profiling log. The 'perf inject' tool will not work.";
+ }
+}
+
+void JitLogger::OpenJitDumpLog() {
+ std::string pid_str = std::to_string(getpid());
+ std::string jitdump_filename = std::string(kLogPrefix) + "/jit-" + pid_str + ".dump";
+
+ jit_dump_file_.reset(OS::CreateEmptyFile(jitdump_filename.c_str()));
+ if (jit_dump_file_ == nullptr) {
+ LOG(ERROR) << "Could not create jit dump file at " << jitdump_filename <<
+ " Are you on a user build? Perf only works on userdebug/eng builds";
+ return;
+ }
+
+ OpenMarkerFile();
+
+ // Continue to write jit-PID.dump file even above OpenMarkerFile() fails.
+ // Even if that means 'perf inject' tool cannot work, developers can still use other tools
+ // to map the samples in perf.data to the information (symbol,address,code) recorded
+ // in the jit-PID.dump file, and still proceed the jitted code analysis.
+ WriteJitDumpHeader();
+}
+
+void JitLogger::WriteJitDumpLog(JitCodeCache* code_cache, ArtMethod* method) {
+ if (jit_dump_file_ != nullptr) {
+ const void* code = method->GetEntryPointFromQuickCompiledCode();
+ size_t code_size = code_cache->GetMemorySizeOfCodePointer(code);
+ std::string method_name = method->PrettyMethod();
+
+ PerfJitCodeLoad jit_code;
+ std::memset(&jit_code, 0, sizeof(jit_code));
+ jit_code.event_ = PerfJitCodeLoad::kLoad;
+ jit_code.size_ = sizeof(jit_code) + method_name.size() + 1 + code_size;
+ jit_code.time_stamp_ = art::NanoTime(); // CLOCK_MONOTONIC clock is required.
+ jit_code.process_id_ = static_cast<uint32_t>(getpid());
+ jit_code.thread_id_ = static_cast<uint32_t>(art::GetTid());
+ jit_code.vma_ = 0x0;
+ jit_code.code_address_ = reinterpret_cast<uint64_t>(code);
+ jit_code.code_size_ = code_size;
+ jit_code.code_id_ = code_index_++;
+
+ // Write one complete jitted method info, including:
+ // - PerfJitCodeLoad structure
+ // - Method name
+ // - Complete generated code of this method
+ //
+ // Use UNUSED() here to avoid compiler warnings.
+ UNUSED(jit_dump_file_->WriteFully(reinterpret_cast<const char*>(&jit_code), sizeof(jit_code)));
+ UNUSED(jit_dump_file_->WriteFully(method_name.c_str(), method_name.size() + 1));
+ UNUSED(jit_dump_file_->WriteFully(code, code_size));
+
+ WriteJitDumpDebugInfo();
+ }
+}
+
+void JitLogger::CloseJitDumpLog() {
+ if (jit_dump_file_ != nullptr) {
+ CloseMarkerFile();
+ UNUSED(jit_dump_file_->Flush());
+ UNUSED(jit_dump_file_->Close());
+ }
+}
+
+} // namespace jit
+} // namespace art
diff --git a/compiler/jit/jit_logger.h b/compiler/jit/jit_logger.h
new file mode 100644
index 0000000..0f8cfe4
--- /dev/null
+++ b/compiler/jit/jit_logger.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_COMPILER_JIT_JIT_LOGGER_H_
+#define ART_COMPILER_JIT_JIT_LOGGER_H_
+
+#include "base/mutex.h"
+#include "compiled_method.h"
+#include "driver/compiler_driver.h"
+#include "driver/compiler_options.h"
+
+namespace art {
+
+class ArtMethod;
+
+namespace jit {
+
+//
+// JitLogger supports two approaches of perf profiling.
+//
+// (1) perf-map:
+// The perf-map mechanism generates perf-PID.map file,
+// which provides simple "address, size, method_name" information to perf,
+// and allows perf to map samples in jit-code-cache to jitted method symbols.
+//
+// Command line Example:
+// $ perf record dalvikvm -Xcompiler-option --generate-debug-info -cp <classpath> Test
+// $ perf report
+// NOTE:
+// - Make sure that the perf-PID.map file is available for 'perf report' tool to access,
+// so that jitted method can be displayed.
+//
+//
+// (2) perf-inject:
+// The perf-inject mechansim generates jit-PID.dump file,
+// which provides rich informations about a jitted method.
+// It allows perf or other profiling tools to do advanced analysis on jitted code,
+// for example instruction level profiling.
+//
+// Command line Example:
+// $ perf record -k mono dalvikvm -Xcompiler-option --generate-debug-info -cp <classpath> Test
+// $ perf inject -i perf.data -o perf.data.jitted
+// $ perf report -i perf.data.jitted
+// $ perf annotate -i perf.data.jitted
+// NOTE:
+// REQUIREMENTS
+// - The 'perf record -k mono' option requires 4.1 (or higher) Linux kernel.
+// - The 'perf inject' (generating jit ELF files feature) requires perf 4.6 (or higher).
+// PERF RECORD
+// - The '-k mono' option tells 'perf record' to use CLOCK_MONOTONIC clock during sampling;
+// which is required by 'perf inject', to make sure that both perf.data and jit-PID.dump
+// have unified clock source for timestamps.
+// PERF INJECT
+// - The 'perf inject' tool injects information from jit-PID.dump into perf.data file,
+// and generates small ELF files (jitted-TID-CODEID.so) for each jitted method.
+// - On Android devices, the jit-PID.dump file is generated in /data/misc/trace/ folder, and
+// such location is recorded in perf.data file.
+// The 'perf inject' tool is going to look for jit-PID.dump and generates small ELF files in
+// this /data/misc/trace/ folder.
+// Make sure that you have the read/write access to /data/misc/trace/ folder.
+// - On non-Android devices, the jit-PID.dump file is generated in /tmp/ folder, and
+// 'perf inject' tool operates on this folder.
+// Make sure that you have the read/write access to /tmp/ folder.
+// - If you are executing 'perf inject' on non-Android devices (host), but perf.data and
+// jit-PID.dump files are adb-pulled from Android devices, make sure that there is a
+// /data/misc/trace/ folder on host, and jit-PID.dump file is copied to this folder.
+// - Currently 'perf inject' doesn't provide option to change the path for jit-PID.dump and
+// generated ELF files.
+// PERF ANNOTATE
+// - The 'perf annotate' tool displays assembly level profiling report.
+// Source code can also be displayed if the ELF file has debug symbols.
+// - Make sure above small ELF files are available for 'perf annotate' tool to access,
+// so that jitted code can be displayed in assembly view.
+//
+class JitLogger {
+ public:
+ JitLogger() : code_index_(0), marker_address_(nullptr) {}
+
+ void OpenLog() {
+ OpenPerfMapLog();
+ OpenJitDumpLog();
+ }
+
+ void WriteLog(JitCodeCache* code_cache, ArtMethod* method)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ WritePerfMapLog(code_cache, method);
+ WriteJitDumpLog(code_cache, method);
+ }
+
+ void CloseLog() {
+ ClosePerfMapLog();
+ CloseJitDumpLog();
+ }
+
+ private:
+ // For perf-map profiling
+ void OpenPerfMapLog();
+ void WritePerfMapLog(JitCodeCache* code_cache, ArtMethod* method)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ void ClosePerfMapLog();
+
+ // For perf-inject profiling
+ void OpenJitDumpLog();
+ void WriteJitDumpLog(JitCodeCache* code_cache, ArtMethod* method)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ void CloseJitDumpLog();
+
+ void OpenMarkerFile();
+ void CloseMarkerFile();
+ void WriteJitDumpHeader();
+ void WriteJitDumpDebugInfo();
+
+ std::unique_ptr<File> perf_file_;
+ std::unique_ptr<File> jit_dump_file_;
+ uint64_t code_index_;
+ void* marker_address_;
+
+ DISALLOW_COPY_AND_ASSIGN(JitLogger);
+};
+
+} // namespace jit
+} // namespace art
+
+#endif // ART_COMPILER_JIT_JIT_LOGGER_H_
diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc
index 8d0f203..8ca8b8a 100644
--- a/compiler/optimizing/code_generator_arm.cc
+++ b/compiler/optimizing/code_generator_arm.cc
@@ -4864,16 +4864,21 @@
case Primitive::kPrimShort:
case Primitive::kPrimChar:
case Primitive::kPrimInt: {
+ Register length;
+ if (maybe_compressed_char_at) {
+ length = locations->GetTemp(0).AsRegister<Register>();
+ uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
+ __ LoadFromOffset(kLoadWord, length, obj, count_offset);
+ codegen_->MaybeRecordImplicitNullCheck(instruction);
+ }
if (index.IsConstant()) {
int32_t const_index = index.GetConstant()->AsIntConstant()->GetValue();
if (maybe_compressed_char_at) {
- Register length = IP;
Label uncompressed_load, done;
- uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
- __ LoadFromOffset(kLoadWord, length, obj, count_offset);
- codegen_->MaybeRecordImplicitNullCheck(instruction);
- __ cmp(length, ShifterOperand(0));
- __ b(&uncompressed_load, GE);
+ __ Lsrs(length, length, 1u); // LSRS has a 16-bit encoding, TST (immediate) does not.
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ b(&uncompressed_load, CS);
__ LoadFromOffset(kLoadUnsignedByte,
out_loc.AsRegister<Register>(),
obj,
@@ -4908,12 +4913,10 @@
}
if (maybe_compressed_char_at) {
Label uncompressed_load, done;
- uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
- Register length = locations->GetTemp(0).AsRegister<Register>();
- __ LoadFromOffset(kLoadWord, length, obj, count_offset);
- codegen_->MaybeRecordImplicitNullCheck(instruction);
- __ cmp(length, ShifterOperand(0));
- __ b(&uncompressed_load, GE);
+ __ Lsrs(length, length, 1u); // LSRS has a 16-bit encoding, TST (immediate) does not.
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ b(&uncompressed_load, CS);
__ ldrb(out_loc.AsRegister<Register>(),
Address(temp, index.AsRegister<Register>(), Shift::LSL, 0));
__ b(&done);
@@ -5318,7 +5321,7 @@
codegen_->MaybeRecordImplicitNullCheck(instruction);
// Mask out compression flag from String's array length.
if (mirror::kUseStringCompression && instruction->IsStringLength()) {
- __ bic(out, out, ShifterOperand(1u << 31));
+ __ Lsr(out, out, 1u);
}
}
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 3d81635..6f55b42 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -2333,13 +2333,22 @@
if (maybe_compressed_char_at) {
uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
length = temps.AcquireW();
- __ Ldr(length, HeapOperand(obj, count_offset));
+ if (instruction->GetArray()->IsIntermediateAddress()) {
+ DCHECK_LT(count_offset, offset);
+ int64_t adjusted_offset = static_cast<int64_t>(count_offset) - static_cast<int64_t>(offset);
+ // Note that `adjusted_offset` is negative, so this will be a LDUR.
+ __ Ldr(length, MemOperand(obj.X(), adjusted_offset));
+ } else {
+ __ Ldr(length, HeapOperand(obj, count_offset));
+ }
codegen_->MaybeRecordImplicitNullCheck(instruction);
}
if (index.IsConstant()) {
if (maybe_compressed_char_at) {
vixl::aarch64::Label uncompressed_load, done;
- __ Tbz(length.W(), kWRegSize - 1, &uncompressed_load);
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ Tbnz(length.W(), 0, &uncompressed_load);
__ Ldrb(Register(OutputCPURegister(instruction)),
HeapOperand(obj, offset + Int64ConstantFrom(index)));
__ B(&done);
@@ -2367,7 +2376,9 @@
}
if (maybe_compressed_char_at) {
vixl::aarch64::Label uncompressed_load, done;
- __ Tbz(length.W(), kWRegSize - 1, &uncompressed_load);
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ Tbnz(length.W(), 0, &uncompressed_load);
__ Ldrb(Register(OutputCPURegister(instruction)),
HeapOperand(temp, XRegisterFrom(index), LSL, 0));
__ B(&done);
@@ -2412,7 +2423,7 @@
codegen_->MaybeRecordImplicitNullCheck(instruction);
// Mask out compression flag from String's array length.
if (mirror::kUseStringCompression && instruction->IsStringLength()) {
- __ And(out.W(), out.W(), Operand(static_cast<int32_t>(INT32_MAX)));
+ __ Lsr(out.W(), out.W(), 1u);
}
}
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index b9814b6..e7039e6 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -3798,16 +3798,21 @@
case Primitive::kPrimShort:
case Primitive::kPrimChar:
case Primitive::kPrimInt: {
+ vixl32::Register length;
+ if (maybe_compressed_char_at) {
+ length = RegisterFrom(locations->GetTemp(0));
+ uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
+ GetAssembler()->LoadFromOffset(kLoadWord, length, obj, count_offset);
+ codegen_->MaybeRecordImplicitNullCheck(instruction);
+ }
if (index.IsConstant()) {
int32_t const_index = index.GetConstant()->AsIntConstant()->GetValue();
if (maybe_compressed_char_at) {
- vixl32::Register length = temps.Acquire();
vixl32::Label uncompressed_load, done;
- uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
- GetAssembler()->LoadFromOffset(kLoadWord, length, obj, count_offset);
- codegen_->MaybeRecordImplicitNullCheck(instruction);
- __ Cmp(length, 0);
- __ B(ge, &uncompressed_load);
+ __ Lsrs(length, length, 1u); // LSRS has a 16-bit encoding, TST (immediate) does not.
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ B(cs, &uncompressed_load);
GetAssembler()->LoadFromOffset(kLoadUnsignedByte,
RegisterFrom(out_loc),
obj,
@@ -3835,12 +3840,10 @@
}
if (maybe_compressed_char_at) {
vixl32::Label uncompressed_load, done;
- uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
- vixl32::Register length = RegisterFrom(locations->GetTemp(0));
- GetAssembler()->LoadFromOffset(kLoadWord, length, obj, count_offset);
- codegen_->MaybeRecordImplicitNullCheck(instruction);
- __ Cmp(length, 0);
- __ B(ge, &uncompressed_load);
+ __ Lsrs(length, length, 1u); // LSRS has a 16-bit encoding, TST (immediate) does not.
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ B(cs, &uncompressed_load);
__ Ldrb(RegisterFrom(out_loc), MemOperand(temp, RegisterFrom(index), vixl32::LSL, 0));
__ B(&done);
__ Bind(&uncompressed_load);
@@ -4219,7 +4222,7 @@
codegen_->MaybeRecordImplicitNullCheck(instruction);
// Mask out compression flag from String's array length.
if (mirror::kUseStringCompression && instruction->IsStringLength()) {
- __ Bic(out, out, 1u << 31);
+ __ Lsr(out, out, 1u);
}
}
diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc
index f19e2fe..f169eb0 100644
--- a/compiler/optimizing/code_generator_mips.cc
+++ b/compiler/optimizing/code_generator_mips.cc
@@ -568,8 +568,7 @@
DCHECK_EQ(type, Primitive::kPrimFloat); // Can only swap a float.
FRegister f1 = loc1.IsFpuRegister() ? loc1.AsFpuRegister<FRegister>()
: loc2.AsFpuRegister<FRegister>();
- Register r2 = loc1.IsRegister() ? loc1.AsRegister<Register>()
- : loc2.AsRegister<Register>();
+ Register r2 = loc1.IsRegister() ? loc1.AsRegister<Register>() : loc2.AsRegister<Register>();
__ Move(TMP, r2);
__ Mfc1(r2, f1);
__ Mtc1(TMP, f1);
@@ -610,10 +609,8 @@
Exchange(loc1.GetStackIndex(), loc2.GetStackIndex(), /* double_slot */ true);
} else if ((loc1.IsRegister() && loc2.IsStackSlot()) ||
(loc1.IsStackSlot() && loc2.IsRegister())) {
- Register reg = loc1.IsRegister() ? loc1.AsRegister<Register>()
- : loc2.AsRegister<Register>();
- intptr_t offset = loc1.IsStackSlot() ? loc1.GetStackIndex()
- : loc2.GetStackIndex();
+ Register reg = loc1.IsRegister() ? loc1.AsRegister<Register>() : loc2.AsRegister<Register>();
+ intptr_t offset = loc1.IsStackSlot() ? loc1.GetStackIndex() : loc2.GetStackIndex();
__ Move(TMP, reg);
__ LoadFromOffset(kLoadWord, reg, SP, offset);
__ StoreToOffset(kStoreWord, TMP, SP, offset);
@@ -623,8 +620,7 @@
: loc2.AsRegisterPairLow<Register>();
Register reg_h = loc1.IsRegisterPair() ? loc1.AsRegisterPairHigh<Register>()
: loc2.AsRegisterPairHigh<Register>();
- intptr_t offset_l = loc1.IsDoubleStackSlot() ? loc1.GetStackIndex()
- : loc2.GetStackIndex();
+ intptr_t offset_l = loc1.IsDoubleStackSlot() ? loc1.GetStackIndex() : loc2.GetStackIndex();
intptr_t offset_h = loc1.IsDoubleStackSlot() ? loc1.GetHighStackIndex(kMipsWordSize)
: loc2.GetHighStackIndex(kMipsWordSize);
__ Move(TMP, reg_l);
@@ -633,6 +629,20 @@
__ Move(TMP, reg_h);
__ LoadFromOffset(kLoadWord, reg_h, SP, offset_h);
__ StoreToOffset(kStoreWord, TMP, SP, offset_h);
+ } else if (loc1.IsFpuRegister() || loc2.IsFpuRegister()) {
+ FRegister reg = loc1.IsFpuRegister() ? loc1.AsFpuRegister<FRegister>()
+ : loc2.AsFpuRegister<FRegister>();
+ intptr_t offset = loc1.IsFpuRegister() ? loc2.GetStackIndex() : loc1.GetStackIndex();
+ if (type == Primitive::kPrimFloat) {
+ __ MovS(FTMP, reg);
+ __ LoadSFromOffset(reg, SP, offset);
+ __ StoreSToOffset(FTMP, SP, offset);
+ } else {
+ DCHECK_EQ(type, Primitive::kPrimDouble);
+ __ MovD(FTMP, reg);
+ __ LoadDFromOffset(reg, SP, offset);
+ __ StoreDToOffset(FTMP, SP, offset);
+ }
} else {
LOG(FATAL) << "Swap between " << loc1 << " and " << loc2 << " is unsupported";
}
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index 7280e87..20bb36d 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -151,7 +151,7 @@
}
__ movl(length_loc.AsRegister<Register>(), array_len);
if (mirror::kUseStringCompression) {
- __ andl(length_loc.AsRegister<Register>(), Immediate(INT32_MAX));
+ __ shrl(length_loc.AsRegister<Register>(), Immediate(1));
}
}
x86_codegen->EmitParallelMoves(
@@ -5243,9 +5243,11 @@
// Branch cases into compressed and uncompressed for each index's type.
uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
NearLabel done, not_compressed;
- __ cmpl(Address(obj, count_offset), Immediate(0));
+ __ testl(Address(obj, count_offset), Immediate(1));
codegen_->MaybeRecordImplicitNullCheck(instruction);
- __ j(kGreaterEqual, ¬_compressed);
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ j(kNotZero, ¬_compressed);
__ movzxb(out, CodeGeneratorX86::ArrayAddress(obj, index, TIMES_1, data_offset));
__ jmp(&done);
__ Bind(¬_compressed);
@@ -5595,7 +5597,7 @@
codegen_->MaybeRecordImplicitNullCheck(instruction);
// Mask out most significant bit in case the array is String's array of char.
if (mirror::kUseStringCompression && instruction->IsStringLength()) {
- __ andl(out, Immediate(INT32_MAX));
+ __ shrl(out, Immediate(1));
}
}
@@ -5654,10 +5656,12 @@
Location array_loc = array_length->GetLocations()->InAt(0);
Address array_len(array_loc.AsRegister<Register>(), len_offset);
if (is_string_compressed_char_at) {
+ // TODO: if index_loc.IsConstant(), compare twice the index (to compensate for
+ // the string compression flag) with the in-memory length and avoid the temporary.
Register length_reg = locations->GetTemp(0).AsRegister<Register>();
__ movl(length_reg, array_len);
codegen_->MaybeRecordImplicitNullCheck(array_length);
- __ andl(length_reg, Immediate(INT32_MAX));
+ __ shrl(length_reg, Immediate(1));
codegen_->GenerateIntCompare(length_reg, index_loc);
} else {
// Checking bounds for general case:
@@ -6589,25 +6593,30 @@
}
}
+static bool IsTypeCheckSlowPathFatal(TypeCheckKind type_check_kind, bool throws_into_catch) {
+ switch (type_check_kind) {
+ case TypeCheckKind::kExactCheck:
+ case TypeCheckKind::kAbstractClassCheck:
+ case TypeCheckKind::kClassHierarchyCheck:
+ case TypeCheckKind::kArrayObjectCheck:
+ return !throws_into_catch && !kEmitCompilerReadBarrier;
+ case TypeCheckKind::kInterfaceCheck:
+ return !throws_into_catch && !kEmitCompilerReadBarrier && !kPoisonHeapReferences;
+ case TypeCheckKind::kArrayCheck:
+ case TypeCheckKind::kUnresolvedCheck:
+ return false;
+ }
+ LOG(FATAL) << "Unreachable";
+ UNREACHABLE();
+}
+
void LocationsBuilderX86::VisitCheckCast(HCheckCast* instruction) {
- LocationSummary::CallKind call_kind = LocationSummary::kNoCall;
bool throws_into_catch = instruction->CanThrowIntoCatchBlock();
TypeCheckKind type_check_kind = instruction->GetTypeCheckKind();
- switch (type_check_kind) {
- case TypeCheckKind::kExactCheck:
- case TypeCheckKind::kAbstractClassCheck:
- case TypeCheckKind::kClassHierarchyCheck:
- case TypeCheckKind::kArrayObjectCheck:
- case TypeCheckKind::kInterfaceCheck:
- call_kind = (throws_into_catch || kEmitCompilerReadBarrier) ?
- LocationSummary::kCallOnSlowPath :
- LocationSummary::kNoCall; // In fact, call on a fatal (non-returning) slow path.
- break;
- case TypeCheckKind::kArrayCheck:
- case TypeCheckKind::kUnresolvedCheck:
- call_kind = LocationSummary::kCallOnSlowPath;
- break;
- }
+ LocationSummary::CallKind call_kind =
+ IsTypeCheckSlowPathFatal(type_check_kind, throws_into_catch)
+ ? LocationSummary::kNoCall
+ : LocationSummary::kCallOnSlowPath;
LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction, call_kind);
locations->SetInAt(0, Location::RequiresRegister());
if (type_check_kind == TypeCheckKind::kInterfaceCheck) {
@@ -6623,23 +6632,6 @@
locations->AddRegisterTemps(NumberOfCheckCastTemps(type_check_kind));
}
-static bool IsTypeCheckSlowPathFatal(TypeCheckKind type_check_kind, bool throws_into_catch) {
- switch (type_check_kind) {
- case TypeCheckKind::kExactCheck:
- case TypeCheckKind::kAbstractClassCheck:
- case TypeCheckKind::kClassHierarchyCheck:
- case TypeCheckKind::kArrayObjectCheck:
- return !throws_into_catch && !kEmitCompilerReadBarrier;
- case TypeCheckKind::kInterfaceCheck:
- return !throws_into_catch && !kEmitCompilerReadBarrier && !kPoisonHeapReferences;
- case TypeCheckKind::kArrayCheck:
- case TypeCheckKind::kUnresolvedCheck:
- return false;
- }
- LOG(FATAL) << "Unreachable";
- UNREACHABLE();
-}
-
void InstructionCodeGeneratorX86::VisitCheckCast(HCheckCast* instruction) {
TypeCheckKind type_check_kind = instruction->GetTypeCheckKind();
LocationSummary* locations = instruction->GetLocations();
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index 1abda7d..a6dd0c1 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -199,7 +199,7 @@
}
__ movl(length_loc.AsRegister<CpuRegister>(), array_len);
if (mirror::kUseStringCompression) {
- __ andl(length_loc.AsRegister<CpuRegister>(), Immediate(INT32_MAX));
+ __ shrl(length_loc.AsRegister<CpuRegister>(), Immediate(1));
}
}
@@ -4732,9 +4732,11 @@
// Branch cases into compressed and uncompressed for each index's type.
uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
NearLabel done, not_compressed;
- __ cmpl(Address(obj, count_offset), Immediate(0));
+ __ testl(Address(obj, count_offset), Immediate(1));
codegen_->MaybeRecordImplicitNullCheck(instruction);
- __ j(kGreaterEqual, ¬_compressed);
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ j(kNotZero, ¬_compressed);
__ movzxb(out, CodeGeneratorX86_64::ArrayAddress(obj, index, TIMES_1, data_offset));
__ jmp(&done);
__ Bind(¬_compressed);
@@ -5066,7 +5068,7 @@
codegen_->MaybeRecordImplicitNullCheck(instruction);
// Mask out most significant bit in case the array is String's array of char.
if (mirror::kUseStringCompression && instruction->IsStringLength()) {
- __ andl(out, Immediate(INT32_MAX));
+ __ shrl(out, Immediate(1));
}
}
@@ -5118,10 +5120,12 @@
Location array_loc = array_length->GetLocations()->InAt(0);
Address array_len(array_loc.AsRegister<CpuRegister>(), len_offset);
if (mirror::kUseStringCompression && instruction->IsStringCharAt()) {
+ // TODO: if index_loc.IsConstant(), compare twice the index (to compensate for
+ // the string compression flag) with the in-memory length and avoid the temporary.
CpuRegister length_reg = CpuRegister(TMP);
__ movl(length_reg, array_len);
codegen_->MaybeRecordImplicitNullCheck(array_length);
- __ andl(length_reg, Immediate(INT32_MAX));
+ __ shrl(length_reg, Immediate(1));
codegen_->GenerateIntCompare(length_reg, index_loc);
} else {
// Checking the bound for general case:
diff --git a/compiler/optimizing/emit_swap_mips_test.cc b/compiler/optimizing/emit_swap_mips_test.cc
new file mode 100644
index 0000000..9dc53e6
--- /dev/null
+++ b/compiler/optimizing/emit_swap_mips_test.cc
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "base/arena_allocator.h"
+#include "code_generator_mips.h"
+#include "optimizing_unit_test.h"
+#include "parallel_move_resolver.h"
+#include "utils/assembler_test_base.h"
+#include "utils/mips/assembler_mips.h"
+
+#include "gtest/gtest.h"
+
+namespace art {
+
+class EmitSwapMipsTest : public ::testing::Test {
+ public:
+ void SetUp() OVERRIDE {
+ allocator_.reset(new ArenaAllocator(&pool_));
+ graph_ = CreateGraph(allocator_.get());
+ isa_features_ = MipsInstructionSetFeatures::FromCppDefines();
+ codegen_ = new (graph_->GetArena()) mips::CodeGeneratorMIPS(graph_,
+ *isa_features_.get(),
+ CompilerOptions());
+ moves_ = new (allocator_.get()) HParallelMove(allocator_.get());
+ test_helper_.reset(
+ new AssemblerTestInfrastructure(GetArchitectureString(),
+ GetAssemblerCmdName(),
+ GetAssemblerParameters(),
+ GetObjdumpCmdName(),
+ GetObjdumpParameters(),
+ GetDisassembleCmdName(),
+ GetDisassembleParameters(),
+ GetAssemblyHeader()));
+ }
+
+ void TearDown() OVERRIDE {
+ allocator_.reset();
+ test_helper_.reset();
+ }
+
+ // Get the typically used name for this architecture.
+ std::string GetArchitectureString() {
+ return "mips";
+ }
+
+ // Get the name of the assembler.
+ std::string GetAssemblerCmdName() {
+ return "as";
+ }
+
+ // Switches to the assembler command.
+ std::string GetAssemblerParameters() {
+ return " --no-warn -32 -march=mips32r2";
+ }
+
+ // Get the name of the objdump.
+ std::string GetObjdumpCmdName() {
+ return "objdump";
+ }
+
+ // Switches to the objdump command.
+ std::string GetObjdumpParameters() {
+ return " -h";
+ }
+
+ // Get the name of the objdump.
+ std::string GetDisassembleCmdName() {
+ return "objdump";
+ }
+
+ // Switches to the objdump command.
+ std::string GetDisassembleParameters() {
+ return " -D -bbinary -mmips:isa32r2";
+ }
+
+ // No need for assembly header here.
+ const char* GetAssemblyHeader() {
+ return nullptr;
+ }
+
+ void DriverWrapper(HParallelMove* move, std::string assembly_text, std::string test_name) {
+ codegen_->GetMoveResolver()->EmitNativeCode(move);
+ assembler_ = codegen_->GetAssembler();
+ assembler_->FinalizeCode();
+ std::unique_ptr<std::vector<uint8_t>> data(new std::vector<uint8_t>(assembler_->CodeSize()));
+ MemoryRegion code(&(*data)[0], data->size());
+ assembler_->FinalizeInstructions(code);
+ test_helper_->Driver(*data, assembly_text, test_name);
+ }
+
+ protected:
+ ArenaPool pool_;
+ HGraph* graph_;
+ HParallelMove* moves_;
+ mips::CodeGeneratorMIPS* codegen_;
+ mips::MipsAssembler* assembler_;
+ std::unique_ptr<ArenaAllocator> allocator_;
+ std::unique_ptr<AssemblerTestInfrastructure> test_helper_;
+ std::unique_ptr<const MipsInstructionSetFeatures> isa_features_;
+};
+
+TEST_F(EmitSwapMipsTest, TwoRegisters) {
+ moves_->AddMove(
+ Location::RegisterLocation(4),
+ Location::RegisterLocation(5),
+ Primitive::kPrimInt,
+ nullptr);
+ moves_->AddMove(
+ Location::RegisterLocation(5),
+ Location::RegisterLocation(4),
+ Primitive::kPrimInt,
+ nullptr);
+ const char* expected =
+ "or $t8, $a1, $zero\n"
+ "or $a1, $a0, $zero\n"
+ "or $a0, $t8, $zero\n";
+ DriverWrapper(moves_, expected, "TwoRegisters");
+}
+
+TEST_F(EmitSwapMipsTest, TwoRegisterPairs) {
+ moves_->AddMove(
+ Location::RegisterPairLocation(4, 5),
+ Location::RegisterPairLocation(6, 7),
+ Primitive::kPrimLong,
+ nullptr);
+ moves_->AddMove(
+ Location::RegisterPairLocation(6, 7),
+ Location::RegisterPairLocation(4, 5),
+ Primitive::kPrimLong,
+ nullptr);
+ const char* expected =
+ "or $t8, $a2, $zero\n"
+ "or $a2, $a0, $zero\n"
+ "or $a0, $t8, $zero\n"
+ "or $t8, $a3, $zero\n"
+ "or $a3, $a1, $zero\n"
+ "or $a1, $t8, $zero\n";
+ DriverWrapper(moves_, expected, "TwoRegisterPairs");
+}
+
+TEST_F(EmitSwapMipsTest, TwoFpuRegistersFloat) {
+ moves_->AddMove(
+ Location::FpuRegisterLocation(4),
+ Location::FpuRegisterLocation(6),
+ Primitive::kPrimFloat,
+ nullptr);
+ moves_->AddMove(
+ Location::FpuRegisterLocation(6),
+ Location::FpuRegisterLocation(4),
+ Primitive::kPrimFloat,
+ nullptr);
+ const char* expected =
+ "mov.s $f8, $f6\n"
+ "mov.s $f6, $f4\n"
+ "mov.s $f4, $f8\n";
+ DriverWrapper(moves_, expected, "TwoFpuRegistersFloat");
+}
+
+TEST_F(EmitSwapMipsTest, TwoFpuRegistersDouble) {
+ moves_->AddMove(
+ Location::FpuRegisterLocation(4),
+ Location::FpuRegisterLocation(6),
+ Primitive::kPrimDouble,
+ nullptr);
+ moves_->AddMove(
+ Location::FpuRegisterLocation(6),
+ Location::FpuRegisterLocation(4),
+ Primitive::kPrimDouble,
+ nullptr);
+ const char* expected =
+ "mov.d $f8, $f6\n"
+ "mov.d $f6, $f4\n"
+ "mov.d $f4, $f8\n";
+ DriverWrapper(moves_, expected, "TwoFpuRegistersDouble");
+}
+
+TEST_F(EmitSwapMipsTest, RegisterAndFpuRegister) {
+ moves_->AddMove(
+ Location::RegisterLocation(4),
+ Location::FpuRegisterLocation(6),
+ Primitive::kPrimFloat,
+ nullptr);
+ moves_->AddMove(
+ Location::FpuRegisterLocation(6),
+ Location::RegisterLocation(4),
+ Primitive::kPrimFloat,
+ nullptr);
+ const char* expected =
+ "or $t8, $a0, $zero\n"
+ "mfc1 $a0, $f6\n"
+ "mtc1 $t8, $f6\n";
+ DriverWrapper(moves_, expected, "RegisterAndFpuRegister");
+}
+
+TEST_F(EmitSwapMipsTest, RegisterPairAndFpuRegister) {
+ moves_->AddMove(
+ Location::RegisterPairLocation(4, 5),
+ Location::FpuRegisterLocation(4),
+ Primitive::kPrimDouble,
+ nullptr);
+ moves_->AddMove(
+ Location::FpuRegisterLocation(4),
+ Location::RegisterPairLocation(4, 5),
+ Primitive::kPrimDouble,
+ nullptr);
+ const char* expected =
+ "mfc1 $t8, $f4\n"
+ "mfc1 $at, $f5\n"
+ "mtc1 $a0, $f4\n"
+ "mtc1 $a1, $f5\n"
+ "or $a0, $t8, $zero\n"
+ "or $a1, $at, $zero\n";
+ DriverWrapper(moves_, expected, "RegisterPairAndFpuRegister");
+}
+
+TEST_F(EmitSwapMipsTest, TwoStackSlots) {
+ moves_->AddMove(
+ Location::StackSlot(52),
+ Location::StackSlot(48),
+ Primitive::kPrimInt,
+ nullptr);
+ moves_->AddMove(
+ Location::StackSlot(48),
+ Location::StackSlot(52),
+ Primitive::kPrimInt,
+ nullptr);
+ const char* expected =
+ "addiu $sp, $sp, -4\n"
+ "sw $v0, 0($sp)\n"
+ "lw $v0, 56($sp)\n"
+ "lw $t8, 52($sp)\n"
+ "sw $v0, 52($sp)\n"
+ "sw $t8, 56($sp)\n"
+ "lw $v0, 0($sp)\n"
+ "addiu $sp, $sp, 4\n";
+ DriverWrapper(moves_, expected, "TwoStackSlots");
+}
+
+TEST_F(EmitSwapMipsTest, TwoDoubleStackSlots) {
+ moves_->AddMove(
+ Location::DoubleStackSlot(56),
+ Location::DoubleStackSlot(48),
+ Primitive::kPrimLong,
+ nullptr);
+ moves_->AddMove(
+ Location::DoubleStackSlot(48),
+ Location::DoubleStackSlot(56),
+ Primitive::kPrimLong,
+ nullptr);
+ const char* expected =
+ "addiu $sp, $sp, -4\n"
+ "sw $v0, 0($sp)\n"
+ "lw $v0, 60($sp)\n"
+ "lw $t8, 52($sp)\n"
+ "sw $v0, 52($sp)\n"
+ "sw $t8, 60($sp)\n"
+ "lw $v0, 64($sp)\n"
+ "lw $t8, 56($sp)\n"
+ "sw $v0, 56($sp)\n"
+ "sw $t8, 64($sp)\n"
+ "lw $v0, 0($sp)\n"
+ "addiu $sp, $sp, 4\n";
+ DriverWrapper(moves_, expected, "TwoDoubleStackSlots");
+}
+
+TEST_F(EmitSwapMipsTest, RegisterAndStackSlot) {
+ moves_->AddMove(
+ Location::RegisterLocation(4),
+ Location::StackSlot(48),
+ Primitive::kPrimInt,
+ nullptr);
+ moves_->AddMove(
+ Location::StackSlot(48),
+ Location::RegisterLocation(4),
+ Primitive::kPrimInt,
+ nullptr);
+ const char* expected =
+ "or $t8, $a0, $zero\n"
+ "lw $a0, 48($sp)\n"
+ "sw $t8, 48($sp)\n";
+ DriverWrapper(moves_, expected, "RegisterAndStackSlot");
+}
+
+TEST_F(EmitSwapMipsTest, RegisterPairAndDoubleStackSlot) {
+ moves_->AddMove(
+ Location::RegisterPairLocation(4, 5),
+ Location::DoubleStackSlot(32),
+ Primitive::kPrimLong,
+ nullptr);
+ moves_->AddMove(
+ Location::DoubleStackSlot(32),
+ Location::RegisterPairLocation(4, 5),
+ Primitive::kPrimLong,
+ nullptr);
+ const char* expected =
+ "or $t8, $a0, $zero\n"
+ "lw $a0, 32($sp)\n"
+ "sw $t8, 32($sp)\n"
+ "or $t8, $a1, $zero\n"
+ "lw $a1, 36($sp)\n"
+ "sw $t8, 36($sp)\n";
+ DriverWrapper(moves_, expected, "RegisterPairAndDoubleStackSlot");
+}
+
+TEST_F(EmitSwapMipsTest, FpuRegisterAndStackSlot) {
+ moves_->AddMove(
+ Location::FpuRegisterLocation(4),
+ Location::StackSlot(48),
+ Primitive::kPrimFloat,
+ nullptr);
+ moves_->AddMove(
+ Location::StackSlot(48),
+ Location::FpuRegisterLocation(4),
+ Primitive::kPrimFloat,
+ nullptr);
+ const char* expected =
+ "mov.s $f8, $f4\n"
+ "lwc1 $f4, 48($sp)\n"
+ "swc1 $f8, 48($sp)\n";
+ DriverWrapper(moves_, expected, "FpuRegisterAndStackSlot");
+}
+
+TEST_F(EmitSwapMipsTest, FpuRegisterAndDoubleStackSlot) {
+ moves_->AddMove(
+ Location::FpuRegisterLocation(4),
+ Location::DoubleStackSlot(48),
+ Primitive::kPrimDouble,
+ nullptr);
+ moves_->AddMove(
+ Location::DoubleStackSlot(48),
+ Location::FpuRegisterLocation(4),
+ Primitive::kPrimDouble,
+ nullptr);
+ const char* expected =
+ "mov.d $f8, $f4\n"
+ "ldc1 $f4, 48($sp)\n"
+ "sdc1 $f8, 48($sp)\n";
+ DriverWrapper(moves_, expected, "FpuRegisterAndDoubleStackSlot");
+}
+
+} // namespace art
diff --git a/compiler/optimizing/instruction_simplifier_arm64.cc b/compiler/optimizing/instruction_simplifier_arm64.cc
index d0dd650..6d107d5 100644
--- a/compiler/optimizing/instruction_simplifier_arm64.cc
+++ b/compiler/optimizing/instruction_simplifier_arm64.cc
@@ -140,13 +140,6 @@
void InstructionSimplifierArm64Visitor::VisitArrayGet(HArrayGet* instruction) {
size_t data_offset = CodeGenerator::GetArrayDataOffset(instruction);
- // Don't move the array pointer if it is charAt because we need to take the count first.
- // TODO: Implement reading (length + compression) for String compression feature from
- // negative offset (count_offset - data_offset) using LDP and clobbering an extra temporary.
- // Note that "LDR (Immediate)" does not have a "signed offset" encoding.
- if (mirror::kUseStringCompression && instruction->IsStringCharAt()) {
- return;
- }
if (TryExtractArrayAccessAddress(instruction,
instruction->GetArray(),
instruction->GetIndex(),
diff --git a/compiler/optimizing/intrinsics_arm.cc b/compiler/optimizing/intrinsics_arm.cc
index 0c39223..8234b24 100644
--- a/compiler/optimizing/intrinsics_arm.cc
+++ b/compiler/optimizing/intrinsics_arm.cc
@@ -1058,7 +1058,6 @@
// Need temporary registers for String compression's feature.
if (mirror::kUseStringCompression) {
locations->AddTemp(Location::RequiresRegister());
- locations->AddTemp(Location::RequiresRegister());
}
locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap);
}
@@ -1074,10 +1073,9 @@
Register temp0 = locations->GetTemp(0).AsRegister<Register>();
Register temp1 = locations->GetTemp(1).AsRegister<Register>();
Register temp2 = locations->GetTemp(2).AsRegister<Register>();
- Register temp3, temp4;
+ Register temp3;
if (mirror::kUseStringCompression) {
temp3 = locations->GetTemp(3).AsRegister<Register>();
- temp4 = locations->GetTemp(4).AsRegister<Register>();
}
Label loop;
@@ -1104,41 +1102,42 @@
// Reference equality check, return 0 if same reference.
__ subs(out, str, ShifterOperand(arg));
__ b(&end, EQ);
+
if (mirror::kUseStringCompression) {
- // Load lengths of this and argument strings.
+ // Load `count` fields of this and argument strings.
__ ldr(temp3, Address(str, count_offset));
- __ ldr(temp4, Address(arg, count_offset));
- // Clean out compression flag from lengths.
- __ bic(temp0, temp3, ShifterOperand(0x80000000));
- __ bic(IP, temp4, ShifterOperand(0x80000000));
+ __ ldr(temp2, Address(arg, count_offset));
+ // Extract lengths from the `count` fields.
+ __ Lsr(temp0, temp3, 1u);
+ __ Lsr(temp1, temp2, 1u);
} else {
// Load lengths of this and argument strings.
__ ldr(temp0, Address(str, count_offset));
- __ ldr(IP, Address(arg, count_offset));
+ __ ldr(temp1, Address(arg, count_offset));
}
// out = length diff.
- __ subs(out, temp0, ShifterOperand(IP));
+ __ subs(out, temp0, ShifterOperand(temp1));
// temp0 = min(len(str), len(arg)).
__ it(GT);
- __ mov(temp0, ShifterOperand(IP), GT);
+ __ mov(temp0, ShifterOperand(temp1), GT);
// Shorter string is empty?
__ CompareAndBranchIfZero(temp0, &end);
if (mirror::kUseStringCompression) {
// Check if both strings using same compression style to use this comparison loop.
- __ eors(temp3, temp3, ShifterOperand(temp4));
- __ b(&different_compression, MI);
- }
- // Store offset of string value in preparation for comparison loop.
- __ mov(temp1, ShifterOperand(value_offset));
- if (mirror::kUseStringCompression) {
+ __ eor(temp2, temp2, ShifterOperand(temp3));
+ __ Lsrs(temp2, temp2, 1u);
+ __ b(&different_compression, CS);
// For string compression, calculate the number of bytes to compare (not chars).
// This could in theory exceed INT32_MAX, so treat temp0 as unsigned.
- __ cmp(temp4, ShifterOperand(0));
- __ it(GE);
- __ add(temp0, temp0, ShifterOperand(temp0), GE);
+ __ Lsls(temp3, temp3, 31u); // Extract purely the compression flag.
+ __ it(NE);
+ __ add(temp0, temp0, ShifterOperand(temp0), NE);
}
+ // Store offset of string value in preparation for comparison loop.
+ __ mov(temp1, ShifterOperand(value_offset));
+
// Assertions that must hold in order to compare multiple characters at a time.
CHECK_ALIGNED(value_offset, 8);
static_assert(IsAligned<8>(kObjectAlignment),
@@ -1198,69 +1197,80 @@
// The comparison is unsigned for string compression, otherwise signed.
__ cmp(temp0, ShifterOperand(temp1, LSR, mirror::kUseStringCompression ? 3 : 4));
__ b(&end, mirror::kUseStringCompression ? LS : LE);
+
// Extract the characters and calculate the difference.
- Label uncompressed_string, continue_process;
if (mirror::kUseStringCompression) {
- __ cmp(temp4, ShifterOperand(0));
- __ b(&uncompressed_string, GE);
- __ bic(temp1, temp1, ShifterOperand(0x7));
- __ b(&continue_process);
+ // For compressed strings we need to clear 0x7 from temp1, for uncompressed we need to clear
+ // 0xf. We also need to prepare the character extraction mask `uncompressed ? 0xffffu : 0xffu`.
+ // The compression flag is now in the highest bit of temp3, so let's play some tricks.
+ __ orr(temp3, temp3, ShifterOperand(0xffu << 23)); // uncompressed ? 0xff800000u : 0x7ff80000u
+ __ bic(temp1, temp1, ShifterOperand(temp3, LSR, 31 - 3)); // &= ~(uncompressed ? 0xfu : 0x7u)
+ __ Asr(temp3, temp3, 7u); // uncompressed ? 0xffff0000u : 0xff0000u.
+ __ Lsr(temp2, temp2, temp1); // Extract second character.
+ __ Lsr(temp3, temp3, 16u); // uncompressed ? 0xffffu : 0xffu
+ __ Lsr(out, IP, temp1); // Extract first character.
+ __ and_(temp2, temp2, ShifterOperand(temp3));
+ __ and_(out, out, ShifterOperand(temp3));
+ } else {
+ __ bic(temp1, temp1, ShifterOperand(0xf));
+ __ Lsr(temp2, temp2, temp1);
+ __ Lsr(out, IP, temp1);
+ __ movt(temp2, 0);
+ __ movt(out, 0);
}
- __ Bind(&uncompressed_string);
- __ bic(temp1, temp1, ShifterOperand(0xf));
- __ Bind(&continue_process);
- __ Lsr(temp2, temp2, temp1);
- __ Lsr(IP, IP, temp1);
- Label calculate_difference, uncompressed_string_extract_chars;
- if (mirror::kUseStringCompression) {
- __ cmp(temp4, ShifterOperand(0));
- __ b(&uncompressed_string_extract_chars, GE);
- __ ubfx(temp2, temp2, 0, 8);
- __ ubfx(IP, IP, 0, 8);
- __ b(&calculate_difference);
- }
- __ Bind(&uncompressed_string_extract_chars);
- __ movt(temp2, 0);
- __ movt(IP, 0);
- __ Bind(&calculate_difference);
- __ sub(out, IP, ShifterOperand(temp2));
- __ b(&end);
+ __ sub(out, out, ShifterOperand(temp2));
if (mirror::kUseStringCompression) {
+ __ b(&end);
+ __ Bind(&different_compression);
+
+ // Comparison for different compression style.
const size_t c_char_size = Primitive::ComponentSize(Primitive::kPrimByte);
DCHECK_EQ(c_char_size, 1u);
- Label loop_arg_compressed, loop_this_compressed, find_diff;
- // Comparison for different compression style.
- // This part is when THIS is compressed and ARG is not.
- __ Bind(&different_compression);
- __ add(temp2, str, ShifterOperand(value_offset));
- __ add(temp3, arg, ShifterOperand(value_offset));
- __ cmp(temp4, ShifterOperand(0));
- __ b(&loop_arg_compressed, LT);
- __ Bind(&loop_this_compressed);
- __ ldrb(IP, Address(temp2, c_char_size, Address::PostIndex));
- __ ldrh(temp4, Address(temp3, char_size, Address::PostIndex));
- __ cmp(IP, ShifterOperand(temp4));
- __ b(&find_diff, NE);
- __ subs(temp0, temp0, ShifterOperand(1));
- __ b(&loop_this_compressed, GT);
- __ b(&end);
+ // We want to free up the temp3, currently holding `str.count`, for comparison.
+ // So, we move it to the bottom bit of the iteration count `temp0` which we tnen
+ // need to treat as unsigned. Start by freeing the bit with an ADD and continue
+ // further down by a LSRS+SBC which will flip the meaning of the flag but allow
+ // `subs temp0, #2; bhi different_compression_loop` to serve as the loop condition.
+ __ add(temp0, temp0, ShifterOperand(temp0)); // Unlike LSL, this ADD is always 16-bit.
+ // `temp1` will hold the compressed data pointer, `temp2` the uncompressed data pointer.
+ __ mov(temp1, ShifterOperand(str));
+ __ mov(temp2, ShifterOperand(arg));
+ __ Lsrs(temp3, temp3, 1u); // Continue the move of the compression flag.
+ __ it(CS, kItThen); // Interleave with selection of temp1 and temp2.
+ __ mov(temp1, ShifterOperand(arg), CS); // Preserves flags.
+ __ mov(temp2, ShifterOperand(str), CS); // Preserves flags.
+ __ sbc(temp0, temp0, ShifterOperand(0)); // Complete the move of the compression flag.
- // This part is when THIS is not compressed and ARG is.
- __ Bind(&loop_arg_compressed);
- __ ldrh(IP, Address(temp2, char_size, Address::PostIndex));
- __ ldrb(temp4, Address(temp3, c_char_size, Address::PostIndex));
- __ cmp(IP, ShifterOperand(temp4));
- __ b(&find_diff, NE);
- __ subs(temp0, temp0, ShifterOperand(1));
- __ b(&loop_arg_compressed, GT);
+ // Adjust temp1 and temp2 from string pointers to data pointers.
+ __ add(temp1, temp1, ShifterOperand(value_offset));
+ __ add(temp2, temp2, ShifterOperand(value_offset));
+
+ Label different_compression_loop;
+ Label different_compression_diff;
+
+ // Main loop for different compression.
+ __ Bind(&different_compression_loop);
+ __ ldrb(IP, Address(temp1, c_char_size, Address::PostIndex));
+ __ ldrh(temp3, Address(temp2, char_size, Address::PostIndex));
+ __ cmp(IP, ShifterOperand(temp3));
+ __ b(&different_compression_diff, NE);
+ __ subs(temp0, temp0, ShifterOperand(2));
+ __ b(&different_compression_loop, HI);
__ b(&end);
// Calculate the difference.
- __ Bind(&find_diff);
- __ sub(out, IP, ShifterOperand(temp4));
+ __ Bind(&different_compression_diff);
+ __ sub(out, IP, ShifterOperand(temp3));
+ // Flip the difference if the `arg` is compressed.
+ // `temp0` contains inverted `str` compression flag, i.e the same as `arg` compression flag.
+ __ Lsrs(temp0, temp0, 1u);
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ it(CC);
+ __ rsb(out, out, ShifterOperand(0), CC);
}
__ Bind(&end);
@@ -1298,7 +1308,7 @@
Register temp1 = locations->GetTemp(1).AsRegister<Register>();
Register temp2 = locations->GetTemp(2).AsRegister<Register>();
- Label loop, preloop;
+ Label loop;
Label end;
Label return_true;
Label return_false;
@@ -1317,6 +1327,10 @@
__ CompareAndBranchIfZero(arg, &return_false);
}
+ // Reference equality check, return true if same reference.
+ __ cmp(str, ShifterOperand(arg));
+ __ b(&return_true, EQ);
+
if (!optimizations.GetArgumentIsString()) {
// Instanceof check for the argument by comparing class fields.
// All string objects must have the same type since String cannot be subclassed.
@@ -1328,48 +1342,44 @@
__ b(&return_false, NE);
}
- // Load lengths of this and argument strings.
+ // Load `count` fields of this and argument strings.
__ ldr(temp, Address(str, count_offset));
__ ldr(temp1, Address(arg, count_offset));
- // Check if lengths are equal, return false if they're not.
+ // Check if `count` fields are equal, return false if they're not.
// Also compares the compression style, if differs return false.
__ cmp(temp, ShifterOperand(temp1));
__ b(&return_false, NE);
- // Return true if both strings are empty.
- if (mirror::kUseStringCompression) {
- // Length needs to be masked out first because 0 is treated as compressed.
- __ bic(temp, temp, ShifterOperand(0x80000000));
- }
+ // Return true if both strings are empty. Even with string compression `count == 0` means empty.
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
__ cbz(temp, &return_true);
- // Reference equality check, return true if same reference.
- __ cmp(str, ShifterOperand(arg));
- __ b(&return_true, EQ);
- // Assertions that must hold in order to compare strings 2 characters at a time.
+ // Assertions that must hold in order to compare strings 4 bytes at a time.
DCHECK_ALIGNED(value_offset, 4);
static_assert(IsAligned<4>(kObjectAlignment), "String data must be aligned for fast compare.");
if (mirror::kUseStringCompression) {
- // If not compressed, directly to fast compare. Else do preprocess on length.
- __ cmp(temp1, ShifterOperand(0));
- __ b(&preloop, GT);
- // Mask out compression flag and adjust length for compressed string (8-bit)
- // as if it is a 16-bit data, new_length = (length + 1) / 2.
- __ add(temp, temp, ShifterOperand(1));
- __ Lsr(temp, temp, 1);
- __ Bind(&preloop);
+ // For string compression, calculate the number of bytes to compare (not chars).
+ // This could in theory exceed INT32_MAX, so treat temp as unsigned.
+ __ Lsrs(temp, temp, 1u); // Extract length and check compression flag.
+ __ it(CS); // If uncompressed,
+ __ add(temp, temp, ShifterOperand(temp), CS); // double the byte count.
}
- // Loop to compare strings 2 characters at a time starting at the front of the string.
- // Ok to do this because strings with an odd length are zero-padded.
+
+ // Store offset of string value in preparation for comparison loop.
__ LoadImmediate(temp1, value_offset);
+
+ // Loop to compare strings 4 bytes at a time starting at the front of the string.
+ // Ok to do this because strings are zero-padded to kObjectAlignment.
__ Bind(&loop);
__ ldr(out, Address(str, temp1));
__ ldr(temp2, Address(arg, temp1));
+ __ add(temp1, temp1, ShifterOperand(sizeof(uint32_t)));
__ cmp(out, ShifterOperand(temp2));
__ b(&return_false, NE);
- __ add(temp1, temp1, ShifterOperand(sizeof(uint32_t)));
- __ subs(temp, temp, ShifterOperand(sizeof(uint32_t) / sizeof(uint16_t)));
- __ b(&loop, GT);
+ // With string compression, we have compared 4 bytes, otherwise 2 chars.
+ __ subs(temp, temp, ShifterOperand(mirror::kUseStringCompression ? 4 : 2));
+ __ b(&loop, HI);
// Return true and exit the function.
// If loop does not result in returning false, we return true.
@@ -2477,8 +2487,8 @@
const uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
// String's length.
__ ldr(IP, Address(srcObj, count_offset));
- __ cmp(IP, ShifterOperand(0));
- __ b(&compressed_string_preloop, LT);
+ __ tst(IP, ShifterOperand(1));
+ __ b(&compressed_string_preloop, EQ);
}
__ add(src_ptr, src_ptr, ShifterOperand(srcBegin, LSL, 1));
@@ -2513,9 +2523,10 @@
__ subs(num_chr, num_chr, ShifterOperand(1));
__ strh(IP, Address(dst_ptr, char_size, Address::PostIndex));
__ b(&remainder, GT);
- __ b(&done);
if (mirror::kUseStringCompression) {
+ __ b(&done);
+
const size_t c_char_size = Primitive::ComponentSize(Primitive::kPrimByte);
DCHECK_EQ(c_char_size, 1u);
// Copy loop for compressed src, copying 1 character (8-bit) to (16-bit) at a time.
diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc
index b9424a3..451abc5 100644
--- a/compiler/optimizing/intrinsics_arm64.cc
+++ b/compiler/optimizing/intrinsics_arm64.cc
@@ -1243,7 +1243,6 @@
// Need temporary registers for String compression's feature.
if (mirror::kUseStringCompression) {
locations->AddTemp(Location::RequiresRegister());
- locations->AddTemp(Location::RequiresRegister());
}
locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap);
}
@@ -1261,10 +1260,9 @@
Register temp0 = WRegisterFrom(locations->GetTemp(0));
Register temp1 = WRegisterFrom(locations->GetTemp(1));
Register temp2 = WRegisterFrom(locations->GetTemp(2));
- Register temp3, temp5;
+ Register temp3;
if (mirror::kUseStringCompression) {
temp3 = WRegisterFrom(locations->GetTemp(3));
- temp5 = WRegisterFrom(locations->GetTemp(4));
}
vixl::aarch64::Label loop;
@@ -1291,68 +1289,65 @@
// Reference equality check, return 0 if same reference.
__ Subs(out, str, arg);
__ B(&end, eq);
+
if (mirror::kUseStringCompression) {
- // Load lengths of this and argument strings.
+ // Load `count` fields of this and argument strings.
__ Ldr(temp3, HeapOperand(str, count_offset));
- __ Ldr(temp5, HeapOperand(arg, count_offset));
+ __ Ldr(temp2, HeapOperand(arg, count_offset));
// Clean out compression flag from lengths.
- __ Bic(temp0, temp3, Operand(static_cast<int32_t>(0x80000000)));
- __ Bic(temp1, temp5, Operand(static_cast<int32_t>(0x80000000)));
+ __ Lsr(temp0, temp3, 1u);
+ __ Lsr(temp1, temp2, 1u);
} else {
// Load lengths of this and argument strings.
__ Ldr(temp0, HeapOperand(str, count_offset));
__ Ldr(temp1, HeapOperand(arg, count_offset));
}
- // Return zero if both strings are empty.
- __ Orr(out, temp0, temp1);
- __ Cbz(out, &end);
// out = length diff.
__ Subs(out, temp0, temp1);
- // temp2 = min(len(str), len(arg)).
- __ Csel(temp2, temp1, temp0, ge);
+ // temp0 = min(len(str), len(arg)).
+ __ Csel(temp0, temp1, temp0, ge);
// Shorter string is empty?
- __ Cbz(temp2, &end);
+ __ Cbz(temp0, &end);
if (mirror::kUseStringCompression) {
// Check if both strings using same compression style to use this comparison loop.
- __ Eor(temp3.W(), temp3, Operand(temp5));
- __ Tbnz(temp3.W(), kWRegSize - 1, &different_compression);
+ __ Eor(temp2, temp2, Operand(temp3));
+ // Interleave with compression flag extraction which is needed for both paths
+ // and also set flags which is needed only for the different compressions path.
+ __ Ands(temp3.W(), temp3.W(), Operand(1));
+ __ Tbnz(temp2, 0, &different_compression); // Does not use flags.
}
// Store offset of string value in preparation for comparison loop.
__ Mov(temp1, value_offset);
if (mirror::kUseStringCompression) {
// For string compression, calculate the number of bytes to compare (not chars).
- // This could be in theory exceed INT32_MAX, so treat temp2 as unsigned.
- vixl::aarch64::Label let_it_signed;
- __ Cmp(temp5, Operand(0));
- __ B(lt, &let_it_signed);
- __ Add(temp2, temp2, Operand(temp2));
- __ Bind(&let_it_signed);
+ // This could in theory exceed INT32_MAX, so treat temp0 as unsigned.
+ __ Lsl(temp0, temp0, temp3);
}
UseScratchRegisterScope scratch_scope(masm);
Register temp4 = scratch_scope.AcquireX();
- // Assertions that must hold in order to compare strings 4 characters at a time.
+ // Assertions that must hold in order to compare strings 8 bytes at a time.
DCHECK_ALIGNED(value_offset, 8);
static_assert(IsAligned<8>(kObjectAlignment), "String of odd length is not zero padded");
const size_t char_size = Primitive::ComponentSize(Primitive::kPrimChar);
DCHECK_EQ(char_size, 2u);
- // Promote temp0 to an X reg, ready for LDR.
- temp0 = temp0.X();
+ // Promote temp2 to an X reg, ready for LDR.
+ temp2 = temp2.X();
// Loop to compare 4x16-bit characters at a time (ok because of string data alignment).
__ Bind(&loop);
__ Ldr(temp4, MemOperand(str.X(), temp1.X()));
- __ Ldr(temp0, MemOperand(arg.X(), temp1.X()));
- __ Cmp(temp4, temp0);
+ __ Ldr(temp2, MemOperand(arg.X(), temp1.X()));
+ __ Cmp(temp4, temp2);
__ B(ne, &find_char_diff);
__ Add(temp1, temp1, char_size * 4);
// With string compression, we have compared 8 bytes, otherwise 4 chars.
- __ Subs(temp2, temp2, (mirror::kUseStringCompression) ? 8 : 4);
- __ B(hi, &loop);
+ __ Subs(temp0, temp0, (mirror::kUseStringCompression) ? 8 : 4);
+ __ B(&loop, hi);
__ B(&end);
// Promote temp1 to an X reg, ready for EOR.
@@ -1361,78 +1356,85 @@
// Find the single character difference.
__ Bind(&find_char_diff);
// Get the bit position of the first character that differs.
- __ Eor(temp1, temp0, temp4);
+ __ Eor(temp1, temp2, temp4);
__ Rbit(temp1, temp1);
__ Clz(temp1, temp1);
+
// If the number of chars remaining <= the index where the difference occurs (0-3), then
// the difference occurs outside the remaining string data, so just return length diff (out).
// Unlike ARM, we're doing the comparison in one go here, without the subtraction at the
// find_char_diff_2nd_cmp path, so it doesn't matter whether the comparison is signed or
// unsigned when string compression is disabled.
// When it's enabled, the comparison must be unsigned.
- __ Cmp(temp2, Operand(temp1.W(), LSR, (mirror::kUseStringCompression) ? 3 : 4));
+ __ Cmp(temp0, Operand(temp1.W(), LSR, (mirror::kUseStringCompression) ? 3 : 4));
__ B(ls, &end);
+
// Extract the characters and calculate the difference.
- vixl::aarch64::Label uncompressed_string, continue_process;
if (mirror:: kUseStringCompression) {
- __ Tbz(temp5, kWRegSize - 1, &uncompressed_string);
__ Bic(temp1, temp1, 0x7);
- __ B(&continue_process);
+ __ Bic(temp1, temp1, Operand(temp3.X(), LSL, 3u));
+ } else {
+ __ Bic(temp1, temp1, 0xf);
}
- __ Bind(&uncompressed_string);
- __ Bic(temp1, temp1, 0xf);
- __ Bind(&continue_process);
-
- __ Lsr(temp0, temp0, temp1);
+ __ Lsr(temp2, temp2, temp1);
__ Lsr(temp4, temp4, temp1);
- vixl::aarch64::Label uncompressed_string_extract_chars;
if (mirror::kUseStringCompression) {
- __ Tbz(temp5, kWRegSize - 1, &uncompressed_string_extract_chars);
- __ And(temp4, temp4, 0xff);
- __ Sub(out, temp4.W(), Operand(temp0.W(), UXTB));
- __ B(&end);
+ // Prioritize the case of compressed strings and calculate such result first.
+ __ Uxtb(temp1, temp4);
+ __ Sub(out, temp1.W(), Operand(temp2.W(), UXTB));
+ __ Tbz(temp3, 0u, &end); // If actually compressed, we're done.
}
- __ Bind(&uncompressed_string_extract_chars);
- __ And(temp4, temp4, 0xffff);
- __ Sub(out, temp4.W(), Operand(temp0.W(), UXTH));
- __ B(&end);
+ __ Uxth(temp4, temp4);
+ __ Sub(out, temp4.W(), Operand(temp2.W(), UXTH));
if (mirror::kUseStringCompression) {
- vixl::aarch64::Label loop_this_compressed, loop_arg_compressed, find_diff;
+ __ B(&end);
+ __ Bind(&different_compression);
+
+ // Comparison for different compression style.
const size_t c_char_size = Primitive::ComponentSize(Primitive::kPrimByte);
DCHECK_EQ(c_char_size, 1u);
- temp0 = temp0.W();
temp1 = temp1.W();
- // Comparison for different compression style.
- // This part is when THIS is compressed and ARG is not.
- __ Bind(&different_compression);
- __ Add(temp0, str, Operand(value_offset));
- __ Add(temp1, arg, Operand(value_offset));
- __ Cmp(temp5, Operand(0));
- __ B(lt, &loop_arg_compressed);
+ temp2 = temp2.W();
+ temp4 = temp4.W();
- __ Bind(&loop_this_compressed);
- __ Ldrb(temp3, MemOperand(temp0.X(), c_char_size, PostIndex));
- __ Ldrh(temp5, MemOperand(temp1.X(), char_size, PostIndex));
- __ Cmp(temp3, Operand(temp5));
- __ B(ne, &find_diff);
- __ Subs(temp2, temp2, 1);
- __ B(gt, &loop_this_compressed);
- __ B(&end);
+ // `temp1` will hold the compressed data pointer, `temp2` the uncompressed data pointer.
+ // Note that flags have been set by the `str` compression flag extraction to `temp3`
+ // before branching to the `different_compression` label.
+ __ Csel(temp1, str, arg, eq); // Pointer to the compressed string.
+ __ Csel(temp2, str, arg, ne); // Pointer to the uncompressed string.
- // This part is when THIS is not compressed and ARG is.
- __ Bind(&loop_arg_compressed);
- __ Ldrh(temp3, MemOperand(temp0.X(), char_size, PostIndex));
- __ Ldrb(temp5, MemOperand(temp1.X(), c_char_size, PostIndex));
- __ Cmp(temp3, Operand(temp5));
- __ B(ne, &find_diff);
- __ Subs(temp2, temp2, 1);
- __ B(gt, &loop_arg_compressed);
+ // We want to free up the temp3, currently holding `str` compression flag, for comparison.
+ // So, we move it to the bottom bit of the iteration count `temp0` which we then need to treat
+ // as unsigned. Start by freeing the bit with a LSL and continue further down by a SUB which
+ // will allow `subs temp0, #2; bhi different_compression_loop` to serve as the loop condition.
+ __ Lsl(temp0, temp0, 1u);
+
+ // Adjust temp1 and temp2 from string pointers to data pointers.
+ __ Add(temp1, temp1, Operand(value_offset));
+ __ Add(temp2, temp2, Operand(value_offset));
+
+ // Complete the move of the compression flag.
+ __ Sub(temp0, temp0, Operand(temp3));
+
+ vixl::aarch64::Label different_compression_loop;
+ vixl::aarch64::Label different_compression_diff;
+
+ __ Bind(&different_compression_loop);
+ __ Ldrb(temp4, MemOperand(temp1.X(), c_char_size, PostIndex));
+ __ Ldrh(temp3, MemOperand(temp2.X(), char_size, PostIndex));
+ __ Subs(temp4, temp4, Operand(temp3));
+ __ B(&different_compression_diff, ne);
+ __ Subs(temp0, temp0, 2);
+ __ B(&different_compression_loop, hi);
__ B(&end);
// Calculate the difference.
- __ Bind(&find_diff);
- __ Sub(out, temp3.W(), Operand(temp5.W(), UXTH));
+ __ Bind(&different_compression_diff);
+ __ Tst(temp0, Operand(1));
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ Cneg(out, temp4, ne);
}
__ Bind(&end);
@@ -1468,7 +1470,7 @@
Register temp1 = WRegisterFrom(locations->GetTemp(0));
Register temp2 = WRegisterFrom(locations->GetTemp(1));
- vixl::aarch64::Label loop, preloop;
+ vixl::aarch64::Label loop;
vixl::aarch64::Label end;
vixl::aarch64::Label return_true;
vixl::aarch64::Label return_false;
@@ -1502,49 +1504,46 @@
__ B(&return_false, ne);
}
- // Load lengths of this and argument strings.
+ // Load `count` fields of this and argument strings.
__ Ldr(temp, MemOperand(str.X(), count_offset));
__ Ldr(temp1, MemOperand(arg.X(), count_offset));
- // Check if lengths are equal, return false if they're not.
+ // Check if `count` fields are equal, return false if they're not.
// Also compares the compression style, if differs return false.
__ Cmp(temp, temp1);
__ B(&return_false, ne);
- // Return true if both strings are empty.
- if (mirror::kUseStringCompression) {
- // Length needs to be masked out first because 0 is treated as compressed.
- __ Bic(temp, temp, Operand(static_cast<int32_t>(0x80000000)));
- }
+ // Return true if both strings are empty. Even with string compression `count == 0` means empty.
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
__ Cbz(temp, &return_true);
- // Assertions that must hold in order to compare strings 4 characters at a time.
+ // Assertions that must hold in order to compare strings 8 bytes at a time.
DCHECK_ALIGNED(value_offset, 8);
static_assert(IsAligned<8>(kObjectAlignment), "String of odd length is not zero padded");
if (mirror::kUseStringCompression) {
- // If not compressed, directly to fast compare. Else do preprocess on length.
- __ Cmp(temp1, Operand(0));
- __ B(&preloop, gt);
- // Mask out compression flag and adjust length for compressed string (8-bit)
- // as if it is a 16-bit data, new_length = (length + 1) / 2
- __ Add(temp, temp, 1);
- __ Lsr(temp, temp, 1);
+ // For string compression, calculate the number of bytes to compare (not chars).
+ // This could in theory exceed INT32_MAX, so treat temp as unsigned.
+ __ Lsr(temp, temp, 1u); // Extract length.
+ __ And(temp1, temp1, Operand(1)); // Extract compression flag.
+ __ Lsl(temp, temp, temp1); // Calculate number of bytes to compare.
}
+ // Store offset of string value in preparation for comparison loop
+ __ Mov(temp1, value_offset);
+
temp1 = temp1.X();
temp2 = temp2.X();
- // Loop to compare strings 4 characters at a time starting at the beginning of the string.
- // Ok to do this because strings are zero-padded to be 8-byte aligned.
- // Store offset of string value in preparation for comparison loop
- __ Bind(&preloop);
- __ Mov(temp1, value_offset);
+ // Loop to compare strings 8 bytes at a time starting at the front of the string.
+ // Ok to do this because strings are zero-padded to kObjectAlignment.
__ Bind(&loop);
__ Ldr(out, MemOperand(str.X(), temp1));
__ Ldr(temp2, MemOperand(arg.X(), temp1));
__ Add(temp1, temp1, Operand(sizeof(uint64_t)));
__ Cmp(out, temp2);
__ B(&return_false, ne);
- __ Sub(temp, temp, Operand(4), SetFlags);
- __ B(&loop, gt);
+ // With string compression, we have compared 8 bytes, otherwise 4 chars.
+ __ Sub(temp, temp, Operand(mirror::kUseStringCompression ? 8 : 4), SetFlags);
+ __ B(&loop, hi);
// Return true and exit the function.
// If loop does not result in returning false, we return true.
@@ -1900,10 +1899,6 @@
locations->AddTemp(Location::RequiresRegister());
locations->AddTemp(Location::RequiresRegister());
locations->AddTemp(Location::RequiresRegister());
- // Need temporary register for String compression feature.
- if (mirror::kUseStringCompression) {
- locations->AddTemp(Location::RequiresRegister());
- }
}
void IntrinsicCodeGeneratorARM64::VisitStringGetCharsNoCheck(HInvoke* invoke) {
@@ -1931,10 +1926,6 @@
Register src_ptr = XRegisterFrom(locations->GetTemp(0));
Register num_chr = XRegisterFrom(locations->GetTemp(1));
Register tmp1 = XRegisterFrom(locations->GetTemp(2));
- Register tmp3;
- if (mirror::kUseStringCompression) {
- tmp3 = WRegisterFrom(locations->GetTemp(3));
- }
UseScratchRegisterScope temps(masm);
Register dst_ptr = temps.AcquireX();
@@ -1957,8 +1948,8 @@
// Location of count in string.
const uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
// String's length.
- __ Ldr(tmp3, MemOperand(srcObj, count_offset));
- __ Tbnz(tmp3, kWRegSize - 1, &compressed_string_preloop);
+ __ Ldr(tmp2, MemOperand(srcObj, count_offset));
+ __ Tbz(tmp2, 0, &compressed_string_preloop);
}
__ Add(src_ptr, src_ptr, Operand(srcBegin, LSL, 1));
diff --git a/compiler/optimizing/intrinsics_arm_vixl.cc b/compiler/optimizing/intrinsics_arm_vixl.cc
index e5240a2..e4bef34 100644
--- a/compiler/optimizing/intrinsics_arm_vixl.cc
+++ b/compiler/optimizing/intrinsics_arm_vixl.cc
@@ -1120,7 +1120,6 @@
// Need temporary registers for String compression's feature.
if (mirror::kUseStringCompression) {
locations->AddTemp(Location::RequiresRegister());
- locations->AddTemp(Location::RequiresRegister());
}
locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap);
}
@@ -1136,10 +1135,9 @@
vixl32::Register temp0 = RegisterFrom(locations->GetTemp(0));
vixl32::Register temp1 = RegisterFrom(locations->GetTemp(1));
vixl32::Register temp2 = RegisterFrom(locations->GetTemp(2));
- vixl32::Register temp3, temp4;
+ vixl32::Register temp3;
if (mirror::kUseStringCompression) {
temp3 = RegisterFrom(locations->GetTemp(3));
- temp4 = RegisterFrom(locations->GetTemp(4));
}
vixl32::Label loop;
@@ -1167,23 +1165,20 @@
__ Subs(out, str, arg);
__ B(eq, &end);
- UseScratchRegisterScope temps(assembler->GetVIXLAssembler());
- vixl32::Register temp_reg = temps.Acquire();
-
if (mirror::kUseStringCompression) {
- // Load lengths of this and argument strings.
+ // Load `count` fields of this and argument strings.
__ Ldr(temp3, MemOperand(str, count_offset));
- __ Ldr(temp4, MemOperand(arg, count_offset));
- // Clean out compression flag from lengths.
- __ Bic(temp0, temp3, 0x80000000);
- __ Bic(temp_reg, temp4, 0x80000000);
+ __ Ldr(temp2, MemOperand(arg, count_offset));
+ // Extract lengths from the `count` fields.
+ __ Lsr(temp0, temp3, 1u);
+ __ Lsr(temp1, temp2, 1u);
} else {
// Load lengths of this and argument strings.
__ Ldr(temp0, MemOperand(str, count_offset));
- __ Ldr(temp_reg, MemOperand(arg, count_offset));
+ __ Ldr(temp1, MemOperand(arg, count_offset));
}
// out = length diff.
- __ Subs(out, temp0, temp_reg);
+ __ Subs(out, temp0, temp1);
// temp0 = min(len(str), len(arg)).
{
@@ -1192,33 +1187,32 @@
CodeBufferCheckScope::kMaximumSize);
__ it(gt);
- __ mov(gt, temp0, temp_reg);
+ __ mov(gt, temp0, temp1);
}
- temps.Release(temp_reg);
// Shorter string is empty?
__ Cbz(temp0, &end);
if (mirror::kUseStringCompression) {
// Check if both strings using same compression style to use this comparison loop.
- __ Eors(temp3, temp3, temp4);
- __ B(mi, &different_compression);
- }
- // Store offset of string value in preparation for comparison loop.
- __ Mov(temp1, value_offset);
- if (mirror::kUseStringCompression) {
+ __ Eors(temp2, temp2, temp3);
+ __ Lsrs(temp2, temp2, 1u);
+ __ B(cs, &different_compression);
// For string compression, calculate the number of bytes to compare (not chars).
// This could in theory exceed INT32_MAX, so treat temp0 as unsigned.
- __ Cmp(temp4, 0);
+ __ Lsls(temp3, temp3, 31u); // Extract purely the compression flag.
AssemblerAccurateScope aas(assembler->GetVIXLAssembler(),
2 * kMaxInstructionSizeInBytes,
CodeBufferCheckScope::kMaximumSize);
- __ it(ge);
- __ add(ge, temp0, temp0, temp0);
+ __ it(ne);
+ __ add(ne, temp0, temp0, temp0);
}
+ // Store offset of string value in preparation for comparison loop.
+ __ Mov(temp1, value_offset);
+
// Assertions that must hold in order to compare multiple characters at a time.
CHECK_ALIGNED(value_offset, 8);
static_assert(IsAligned<8>(kObjectAlignment),
@@ -1227,10 +1221,12 @@
const size_t char_size = Primitive::ComponentSize(Primitive::kPrimChar);
DCHECK_EQ(char_size, 2u);
+ UseScratchRegisterScope temps(assembler->GetVIXLAssembler());
+
vixl32::Label find_char_diff_2nd_cmp;
// Unrolled loop comparing 4x16-bit chars per iteration (ok because of string data alignment).
__ Bind(&loop);
- temp_reg = temps.Acquire();
+ vixl32::Register temp_reg = temps.Acquire();
__ Ldr(temp_reg, MemOperand(str, temp1));
__ Ldr(temp2, MemOperand(arg, temp1));
__ Cmp(temp_reg, temp2);
@@ -1279,72 +1275,92 @@
// The comparison is unsigned for string compression, otherwise signed.
__ Cmp(temp0, Operand(temp1, vixl32::LSR, (mirror::kUseStringCompression ? 3 : 4)));
__ B((mirror::kUseStringCompression ? ls : le), &end);
+
// Extract the characters and calculate the difference.
- vixl32::Label uncompressed_string, continue_process;
if (mirror::kUseStringCompression) {
- __ Cmp(temp4, 0);
- __ B(ge, &uncompressed_string);
- __ Bic(temp1, temp1, 0x7);
- __ B(&continue_process);
+ // For compressed strings we need to clear 0x7 from temp1, for uncompressed we need to clear
+ // 0xf. We also need to prepare the character extraction mask `uncompressed ? 0xffffu : 0xffu`.
+ // The compression flag is now in the highest bit of temp3, so let's play some tricks.
+ __ orr(temp3, temp3, 0xffu << 23); // uncompressed ? 0xff800000u : 0x7ff80000u
+ __ bic(temp1, temp1, Operand(temp3, vixl32::LSR, 31 - 3)); // &= ~(uncompressed ? 0xfu : 0x7u)
+ __ Asr(temp3, temp3, 7u); // uncompressed ? 0xffff0000u : 0xff0000u.
+ __ Lsr(temp2, temp2, temp1); // Extract second character.
+ __ Lsr(temp3, temp3, 16u); // uncompressed ? 0xffffu : 0xffu
+ __ Lsr(out, temp_reg, temp1); // Extract first character.
+ __ and_(temp2, temp2, temp3);
+ __ and_(out, out, temp3);
+ } else {
+ __ bic(temp1, temp1, 0xf);
+ __ Lsr(temp2, temp2, temp1);
+ __ Lsr(out, temp_reg, temp1);
+ __ movt(temp2, 0);
+ __ movt(out, 0);
}
- __ Bind(&uncompressed_string);
- __ Bic(temp1, temp1, 0xf);
- __ Bind(&continue_process);
- __ Lsr(temp2, temp2, temp1);
- __ Lsr(temp_reg, temp_reg, temp1);
- vixl32::Label calculate_difference, uncompressed_string_extract_chars;
- if (mirror::kUseStringCompression) {
- __ Cmp(temp4, 0);
- __ B(ge, &uncompressed_string_extract_chars);
- __ Ubfx(temp2, temp2, 0, 8);
- __ Ubfx(temp_reg, temp_reg, 0, 8);
- __ B(&calculate_difference);
- }
- __ Bind(&uncompressed_string_extract_chars);
- __ Movt(temp2, 0);
- __ Movt(temp_reg, 0);
- __ Bind(&calculate_difference);
- __ Sub(out, temp_reg, temp2);
+ __ Sub(out, out, temp2);
temps.Release(temp_reg);
- __ B(&end);
if (mirror::kUseStringCompression) {
+ __ B(&end);
+ __ Bind(&different_compression);
+
+ // Comparison for different compression style.
const size_t c_char_size = Primitive::ComponentSize(Primitive::kPrimByte);
DCHECK_EQ(c_char_size, 1u);
- vixl32::Label loop_arg_compressed, loop_this_compressed, find_diff;
- // Comparison for different compression style.
- // This part is when THIS is compressed and ARG is not.
- __ Bind(&different_compression);
- __ Add(temp2, str, value_offset);
- __ Add(temp3, arg, value_offset);
- __ Cmp(temp4, 0);
- __ B(lt, &loop_arg_compressed);
- __ Bind(&loop_this_compressed);
+ // We want to free up the temp3, currently holding `str.count`, for comparison.
+ // So, we move it to the bottom bit of the iteration count `temp0` which we tnen
+ // need to treat as unsigned. Start by freeing the bit with an ADD and continue
+ // further down by a LSRS+SBC which will flip the meaning of the flag but allow
+ // `subs temp0, #2; bhi different_compression_loop` to serve as the loop condition.
+ __ add(temp0, temp0, temp0); // Unlike LSL, this ADD is always 16-bit.
+ // `temp1` will hold the compressed data pointer, `temp2` the uncompressed data pointer.
+ __ mov(temp1, str);
+ __ mov(temp2, arg);
+ __ Lsrs(temp3, temp3, 1u); // Continue the move of the compression flag.
+ {
+ AssemblerAccurateScope aas(assembler->GetVIXLAssembler(),
+ 3 * kMaxInstructionSizeInBytes,
+ CodeBufferCheckScope::kMaximumSize);
+ __ itt(cs); // Interleave with selection of temp1 and temp2.
+ __ mov(cs, temp1, arg); // Preserves flags.
+ __ mov(cs, temp2, str); // Preserves flags.
+ }
+ __ sbc(temp0, temp0, 0); // Complete the move of the compression flag.
+
+ // Adjust temp1 and temp2 from string pointers to data pointers.
+ __ add(temp1, temp1, value_offset);
+ __ add(temp2, temp2, value_offset);
+
+ vixl32::Label different_compression_loop;
+ vixl32::Label different_compression_diff;
+
+ // Main loop for different compression.
temp_reg = temps.Acquire();
- __ Ldrb(temp_reg, MemOperand(temp2, c_char_size, PostIndex));
- __ Ldrh(temp4, MemOperand(temp3, char_size, PostIndex));
- __ Cmp(temp_reg, temp4);
- __ B(ne, &find_diff);
- __ Subs(temp0, temp0, 1);
- __ B(gt, &loop_this_compressed);
- __ B(&end);
-
- // This part is when THIS is not compressed and ARG is.
- __ Bind(&loop_arg_compressed);
- __ Ldrh(temp_reg, MemOperand(temp2, char_size, PostIndex));
- __ Ldrb(temp4, MemOperand(temp3, c_char_size, PostIndex));
- __ Cmp(temp_reg, temp4);
- __ B(ne, &find_diff);
- __ Subs(temp0, temp0, 1);
- __ B(gt, &loop_arg_compressed);
+ __ Bind(&different_compression_loop);
+ __ Ldrb(temp_reg, MemOperand(temp1, c_char_size, PostIndex));
+ __ Ldrh(temp3, MemOperand(temp2, char_size, PostIndex));
+ __ cmp(temp_reg, temp3);
+ __ B(ne, &different_compression_diff);
+ __ Subs(temp0, temp0, 2);
+ __ B(hi, &different_compression_loop);
__ B(&end);
// Calculate the difference.
- __ Bind(&find_diff);
- __ Sub(out, temp_reg, temp4);
+ __ Bind(&different_compression_diff);
+ __ Sub(out, temp_reg, temp3);
temps.Release(temp_reg);
+ // Flip the difference if the `arg` is compressed.
+ // `temp0` contains inverted `str` compression flag, i.e the same as `arg` compression flag.
+ __ Lsrs(temp0, temp0, 1u);
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+
+ AssemblerAccurateScope aas(assembler->GetVIXLAssembler(),
+ 2 * kMaxInstructionSizeInBytes,
+ CodeBufferCheckScope::kMaximumSize);
+ __ it(cc);
+ __ rsb(cc, out, out, 0);
}
__ Bind(&end);
@@ -1382,7 +1398,7 @@
vixl32::Register temp1 = RegisterFrom(locations->GetTemp(1));
vixl32::Register temp2 = RegisterFrom(locations->GetTemp(2));
- vixl32::Label loop, preloop;
+ vixl32::Label loop;
vixl32::Label end;
vixl32::Label return_true;
vixl32::Label return_false;
@@ -1401,6 +1417,10 @@
__ Cbz(arg, &return_false);
}
+ // Reference equality check, return true if same reference.
+ __ Cmp(str, arg);
+ __ B(eq, &return_true);
+
if (!optimizations.GetArgumentIsString()) {
// Instanceof check for the argument by comparing class fields.
// All string objects must have the same type since String cannot be subclassed.
@@ -1412,48 +1432,47 @@
__ B(ne, &return_false);
}
- // Load lengths of this and argument strings.
+ // Load `count` fields of this and argument strings.
__ Ldr(temp, MemOperand(str, count_offset));
__ Ldr(temp1, MemOperand(arg, count_offset));
- // Check if lengths are equal, return false if they're not.
+ // Check if `count` fields are equal, return false if they're not.
// Also compares the compression style, if differs return false.
__ Cmp(temp, temp1);
__ B(ne, &return_false);
- // Return true if both strings are empty.
- if (mirror::kUseStringCompression) {
- // Length needs to be masked out first because 0 is treated as compressed.
- __ Bic(temp, temp, 0x80000000);
- }
+ // Return true if both strings are empty. Even with string compression `count == 0` means empty.
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
__ Cbz(temp, &return_true);
- // Reference equality check, return true if same reference.
- __ Cmp(str, arg);
- __ B(eq, &return_true);
- // Assertions that must hold in order to compare strings 2 characters at a time.
+ // Assertions that must hold in order to compare strings 4 bytes at a time.
DCHECK_ALIGNED(value_offset, 4);
static_assert(IsAligned<4>(kObjectAlignment), "String data must be aligned for fast compare.");
if (mirror::kUseStringCompression) {
- // If not compressed, directly to fast compare. Else do preprocess on length.
- __ Cmp(temp1, 0);
- __ B(gt, &preloop);
- // Mask out compression flag and adjust length for compressed string (8-bit)
- // as if it is a 16-bit data, new_length = (length + 1) / 2.
- __ Add(temp, temp, 1);
- __ Lsr(temp, temp, 1);
- __ Bind(&preloop);
+ // For string compression, calculate the number of bytes to compare (not chars).
+ // This could in theory exceed INT32_MAX, so treat temp as unsigned.
+ __ Lsrs(temp, temp, 1u); // Extract length and check compression flag.
+ AssemblerAccurateScope aas(assembler->GetVIXLAssembler(),
+ 2 * kMaxInstructionSizeInBytes,
+ CodeBufferCheckScope::kMaximumSize);
+ __ it(cs); // If uncompressed,
+ __ add(cs, temp, temp, temp); // double the byte count.
}
- // Loop to compare strings 2 characters at a time starting at the front of the string.
- // Ok to do this because strings with an odd length are zero-padded.
+
+ // Store offset of string value in preparation for comparison loop.
__ Mov(temp1, value_offset);
+
+ // Loop to compare strings 4 bytes at a time starting at the front of the string.
+ // Ok to do this because strings are zero-padded to kObjectAlignment.
__ Bind(&loop);
__ Ldr(out, MemOperand(str, temp1));
__ Ldr(temp2, MemOperand(arg, temp1));
+ __ Add(temp1, temp1, sizeof(uint32_t));
__ Cmp(out, temp2);
__ B(ne, &return_false);
- __ Add(temp1, temp1, sizeof(uint32_t));
- __ Subs(temp, temp, sizeof(uint32_t) / sizeof(uint16_t));
- __ B(gt, &loop);
+ // With string compression, we have compared 4 bytes, otherwise 2 chars.
+ __ Subs(temp, temp, mirror::kUseStringCompression ? 4 : 2);
+ __ B(hi, &loop);
// Return true and exit the function.
// If loop does not result in returning false, we return true.
@@ -2547,9 +2566,9 @@
temp = temps.Acquire();
// String's length.
__ Ldr(temp, MemOperand(srcObj, count_offset));
- __ Cmp(temp, 0);
+ __ Tst(temp, 1);
temps.Release(temp);
- __ B(lt, &compressed_string_preloop);
+ __ B(eq, &compressed_string_preloop);
}
__ Add(src_ptr, src_ptr, Operand(srcBegin, vixl32::LSL, 1));
@@ -2588,9 +2607,10 @@
__ Strh(temp, MemOperand(dst_ptr, char_size, PostIndex));
temps.Release(temp);
__ B(gt, &remainder);
- __ B(&done);
if (mirror::kUseStringCompression) {
+ __ B(&done);
+
const size_t c_char_size = Primitive::ComponentSize(Primitive::kPrimByte);
DCHECK_EQ(c_char_size, 1u);
// Copy loop for compressed src, copying 1 character (8-bit) to (16-bit) at a time.
diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc
index bac98d5..06ab46f 100644
--- a/compiler/optimizing/intrinsics_x86.cc
+++ b/compiler/optimizing/intrinsics_x86.cc
@@ -1408,21 +1408,22 @@
// compression style is decided on alloc.
__ cmpl(ecx, Address(arg, count_offset));
__ j(kNotEqual, &return_false);
+ // Return true if strings are empty. Even with string compression `count == 0` means empty.
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ jecxz(&return_true);
if (mirror::kUseStringCompression) {
NearLabel string_uncompressed;
- // Differ cases into both compressed or both uncompressed. Different compression style
- // is cut above.
- __ cmpl(ecx, Immediate(0));
- __ j(kGreaterEqual, &string_uncompressed);
+ // Extract length and differentiate between both compressed or both uncompressed.
+ // Different compression style is cut above.
+ __ shrl(ecx, Immediate(1));
+ __ j(kCarrySet, &string_uncompressed);
// Divide string length by 2, rounding up, and continue as if uncompressed.
- // Merge clearing the compression flag (+0x80000000) with +1 for rounding.
- __ addl(ecx, Immediate(0x80000001));
+ __ addl(ecx, Immediate(1));
__ shrl(ecx, Immediate(1));
__ Bind(&string_uncompressed);
}
- // Return true if strings are empty.
- __ jecxz(&return_true);
// Load starting addresses of string values into ESI/EDI as required for repe_cmpsl instruction.
__ leal(esi, Address(str, value_offset));
__ leal(edi, Address(arg, value_offset));
@@ -1535,21 +1536,24 @@
// Location of count within the String object.
int32_t count_offset = mirror::String::CountOffset().Int32Value();
- // Load string length, i.e., the count field of the string.
+ // Load the count field of the string containing the length and compression flag.
__ movl(string_length, Address(string_obj, count_offset));
- if (mirror::kUseStringCompression) {
- string_length_flagged = locations->GetTemp(2).AsRegister<Register>();
- __ movl(string_length_flagged, string_length);
- // Mask out first bit used as compression flag.
- __ andl(string_length, Immediate(INT32_MAX));
- }
- // Do a zero-length check.
+ // Do a zero-length check. Even with string compression `count == 0` means empty.
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
// TODO: Support jecxz.
NearLabel not_found_label;
__ testl(string_length, string_length);
__ j(kEqual, ¬_found_label);
+ if (mirror::kUseStringCompression) {
+ string_length_flagged = locations->GetTemp(2).AsRegister<Register>();
+ __ movl(string_length_flagged, string_length);
+ // Extract the length and shift out the least significant bit used as compression flag.
+ __ shrl(string_length, Immediate(1));
+ }
+
if (start_at_zero) {
// Number of chars to scan is the same as the string length.
__ movl(counter, string_length);
@@ -1570,8 +1574,8 @@
if (mirror::kUseStringCompression) {
NearLabel modify_counter, offset_uncompressed_label;
- __ cmpl(string_length_flagged, Immediate(0));
- __ j(kGreaterEqual, &offset_uncompressed_label);
+ __ testl(string_length_flagged, Immediate(1));
+ __ j(kNotZero, &offset_uncompressed_label);
// Move to the start of the string: string_obj + value_offset + start_index.
__ leal(string_obj, Address(string_obj, counter, ScaleFactor::TIMES_1, value_offset));
__ jmp(&modify_counter);
@@ -1593,8 +1597,8 @@
if (mirror::kUseStringCompression) {
NearLabel uncompressed_string_comparison;
NearLabel comparison_done;
- __ cmpl(string_length_flagged, Immediate(0));
- __ j(kGreater, &uncompressed_string_comparison);
+ __ testl(string_length_flagged, Immediate(1));
+ __ j(kNotZero, &uncompressed_string_comparison);
// Check if EAX (search_value) is ASCII.
__ cmpl(search_value, Immediate(127));
@@ -1787,8 +1791,10 @@
__ cfi().AdjustCFAOffset(stack_adjust);
NearLabel copy_loop, copy_uncompressed;
- __ cmpl(Address(obj, count_offset), Immediate(0));
- __ j(kGreaterEqual, ©_uncompressed);
+ __ testl(Address(obj, count_offset), Immediate(1));
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ j(kNotZero, ©_uncompressed);
// Compute the address of the source string by adding the number of chars from
// the source beginning to the value offset of a string.
__ leal(ESI, CodeGeneratorX86::ArrayAddress(obj, srcBegin, TIMES_1, value_offset));
diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc
index 01577f7..2ea8670 100644
--- a/compiler/optimizing/intrinsics_x86_64.cc
+++ b/compiler/optimizing/intrinsics_x86_64.cc
@@ -1574,20 +1574,23 @@
// compression style is decided on alloc.
__ cmpl(rcx, Address(arg, count_offset));
__ j(kNotEqual, &return_false);
+ // Return true if both strings are empty. Even with string compression `count == 0` means empty.
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ jrcxz(&return_true);
if (mirror::kUseStringCompression) {
NearLabel string_uncompressed;
- // Both string are compressed.
- __ cmpl(rcx, Immediate(0));
- __ j(kGreaterEqual, &string_uncompressed);
+ // Extract length and differentiate between both compressed or both uncompressed.
+ // Different compression style is cut above.
+ __ shrl(rcx, Immediate(1));
+ __ j(kCarrySet, &string_uncompressed);
// Divide string length by 2, rounding up, and continue as if uncompressed.
// Merge clearing the compression flag with +1 for rounding.
- __ addl(rcx, Immediate(static_cast<int32_t>(0x80000001)));
+ __ addl(rcx, Immediate(1));
__ shrl(rcx, Immediate(1));
__ Bind(&string_uncompressed);
}
- // Return true if both strings are empty.
- __ jrcxz(&return_true);
// Load starting addresses of string values into RSI/RDI as required for repe_cmpsq instruction.
__ leal(rsi, Address(str, value_offset));
__ leal(rdi, Address(arg, value_offset));
@@ -1694,21 +1697,22 @@
// Location of count within the String object.
int32_t count_offset = mirror::String::CountOffset().Int32Value();
- // Load string length, i.e., the count field of the string.
+ // Load the count field of the string containing the length and compression flag.
__ movl(string_length, Address(string_obj, count_offset));
- if (mirror::kUseStringCompression) {
- // Use TMP to keep string_length_flagged.
- __ movl(CpuRegister(TMP), string_length);
- // Mask out first bit used as compression flag.
- __ andl(string_length, Immediate(INT32_MAX));
- }
- // Do a length check.
+ // Do a zero-length check. Even with string compression `count == 0` means empty.
// TODO: Support jecxz.
NearLabel not_found_label;
__ testl(string_length, string_length);
__ j(kEqual, ¬_found_label);
+ if (mirror::kUseStringCompression) {
+ // Use TMP to keep string_length_flagged.
+ __ movl(CpuRegister(TMP), string_length);
+ // Mask out first bit used as compression flag.
+ __ shrl(string_length, Immediate(1));
+ }
+
if (start_at_zero) {
// Number of chars to scan is the same as the string length.
__ movl(counter, string_length);
@@ -1728,8 +1732,8 @@
if (mirror::kUseStringCompression) {
NearLabel modify_counter, offset_uncompressed_label;
- __ cmpl(CpuRegister(TMP), Immediate(0));
- __ j(kGreaterEqual, &offset_uncompressed_label);
+ __ testl(CpuRegister(TMP), Immediate(1));
+ __ j(kNotZero, &offset_uncompressed_label);
__ leaq(string_obj, Address(string_obj, counter, ScaleFactor::TIMES_1, value_offset));
__ jmp(&modify_counter);
// Move to the start of the string: string_obj + value_offset + 2 * start_index.
@@ -1747,8 +1751,8 @@
if (mirror::kUseStringCompression) {
NearLabel uncompressed_string_comparison;
NearLabel comparison_done;
- __ cmpl(CpuRegister(TMP), Immediate(0));
- __ j(kGreater, &uncompressed_string_comparison);
+ __ testl(CpuRegister(TMP), Immediate(1));
+ __ j(kNotZero, &uncompressed_string_comparison);
// Check if RAX (search_value) is ASCII.
__ cmpl(search_value, Immediate(127));
__ j(kGreater, ¬_found_label);
@@ -1931,8 +1935,10 @@
// Location of count in string.
const uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
- __ cmpl(Address(obj, count_offset), Immediate(0));
- __ j(kGreaterEqual, ©_uncompressed);
+ __ testl(Address(obj, count_offset), Immediate(1));
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ j(kNotZero, ©_uncompressed);
// Compute the address of the source string by adding the number of chars from
// the source beginning to the value offset of a string.
__ leaq(CpuRegister(RSI),
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index 550f8c7..3a83eaf 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -1769,12 +1769,15 @@
.cfi_rel_offset r10, 4
.cfi_rel_offset r11, 8
.cfi_rel_offset lr, 12
+#if (STRING_COMPRESSION_FEATURE)
+ ldr r4, [r0, #MIRROR_STRING_COUNT_OFFSET]
+#else
ldr r3, [r0, #MIRROR_STRING_COUNT_OFFSET]
+#endif
add r0, #MIRROR_STRING_VALUE_OFFSET
#if (STRING_COMPRESSION_FEATURE)
/* r4 count (with flag) and r3 holds actual length */
- mov r4, r3
- bic r3, #2147483648
+ lsr r3, r4, #1
#endif
/* Clamp start to [0..count] */
cmp r2, #0
@@ -1789,8 +1792,8 @@
/* Build pointer to start of data to compare and pre-bias */
#if (STRING_COMPRESSION_FEATURE)
- cmp r4, #0
- blt .Lstring_indexof_compressed
+ lsrs r4, r4, #1
+ bcc .Lstring_indexof_compressed
#endif
add r0, r0, r2, lsl #1
sub r0, #2
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index d8ebe26..73bca03 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -2403,12 +2403,15 @@
* w2: Starting offset in string data
*/
ENTRY art_quick_indexof
+#if (STRING_COMPRESSION_FEATURE)
+ ldr w4, [x0, #MIRROR_STRING_COUNT_OFFSET]
+#else
ldr w3, [x0, #MIRROR_STRING_COUNT_OFFSET]
+#endif
add x0, x0, #MIRROR_STRING_VALUE_OFFSET
#if (STRING_COMPRESSION_FEATURE)
/* w4 holds count (with flag) and w3 holds actual length */
- mov w4, w3
- and w3, w3, #2147483647
+ lsr w3, w4, #1
#endif
/* Clamp start to [0..count] */
cmp w2, #0
@@ -2420,7 +2423,7 @@
mov x5, x0
#if (STRING_COMPRESSION_FEATURE)
- tbnz w4, #31, .Lstring_indexof_compressed
+ tbz w4, #0, .Lstring_indexof_compressed
#endif
/* Build pointer to start of data to compare and pre-bias */
add x0, x0, x2, lsl #1
diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S
index 635bfa3..761a510 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -2035,15 +2035,14 @@
lea MIRROR_STRING_VALUE_OFFSET(%ecx), %edi
#if (STRING_COMPRESSION_FEATURE)
/* Differ cases */
- cmpl LITERAL(0), %edx
- jl .Lstring_compareto_this_is_compressed
- cmpl LITERAL(0), %ebx
- jl .Lstring_compareto_that_is_compressed
+ shrl LITERAL(1), %edx
+ jnc .Lstring_compareto_this_is_compressed
+ shrl LITERAL(1), %ebx
+ jnc .Lstring_compareto_that_is_compressed
jmp .Lstring_compareto_both_not_compressed
.Lstring_compareto_this_is_compressed:
- andl LITERAL(0x7FFFFFFF), %edx
- cmpl LITERAL(0), %ebx
- jl .Lstring_compareto_both_compressed
+ shrl LITERAL(1), %ebx
+ jnc .Lstring_compareto_both_compressed
/* If (this->IsCompressed() && that->IsCompressed() == false) */
mov %edx, %eax
subl %ebx, %eax
@@ -2061,7 +2060,6 @@
cmovne %edx, %eax // return eax = *(this_cur_char) - *(that_cur_char)
jmp .Lstring_compareto_return
.Lstring_compareto_that_is_compressed:
- andl LITERAL(0x7FFFFFFF), %ebx
mov %edx, %eax
subl %ebx, %eax
mov %edx, %ecx
@@ -2078,7 +2076,6 @@
cmovne %edx, %eax
jmp .Lstring_compareto_return // return eax = *(this_cur_char) - *(that_cur_char)
.Lstring_compareto_both_compressed:
- andl LITERAL(0x7FFFFFFF), %ebx
/* Calculate min length and count diff */
mov %edx, %ecx
mov %edx, %eax
diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
index 72a03eb..20ee3f5 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -2142,15 +2142,14 @@
leal MIRROR_STRING_VALUE_OFFSET(%esi), %esi
#if (STRING_COMPRESSION_FEATURE)
/* Differ cases */
- cmpl LITERAL(0), %r8d
- jl .Lstring_compareto_this_is_compressed
- cmpl LITERAL(0), %r9d
- jl .Lstring_compareto_that_is_compressed
+ shrl LITERAL(1), %r8d
+ jnc .Lstring_compareto_this_is_compressed
+ shrl LITERAL(1), %r9d
+ jnc .Lstring_compareto_that_is_compressed
jmp .Lstring_compareto_both_not_compressed
.Lstring_compareto_this_is_compressed:
- andl LITERAL(0x7FFFFFFF), %r8d
- cmpl LITERAL(0), %r9d
- jl .Lstring_compareto_both_compressed
+ shrl LITERAL(1), %r9d
+ jnc .Lstring_compareto_both_compressed
/* Comparison this (8-bit) and that (16-bit) */
mov %r8d, %eax
subl %r9d, %eax
@@ -2169,7 +2168,6 @@
.Lstring_compareto_keep_length1:
ret
.Lstring_compareto_that_is_compressed:
- andl LITERAL(0x7FFFFFFF), %r9d
movl %r8d, %eax
subl %r9d, %eax
mov %r8d, %ecx
@@ -2187,7 +2185,6 @@
.Lstring_compareto_keep_length2:
ret
.Lstring_compareto_both_compressed:
- andl LITERAL(0x7FFFFFFF), %r9d
/* Calculate min length and count diff */
movl %r8d, %ecx
movl %r8d, %eax
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 6bf7e15..2257fd6 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -1097,10 +1097,12 @@
return;
}
DCHECK_GE(start, 0);
- DCHECK_GE(end, string->GetLength());
+ DCHECK_LE(start, end);
+ DCHECK_LE(end, string->GetLength());
StackHandleScope<1> hs(self);
Handle<mirror::CharArray> h_char_array(
hs.NewHandle(shadow_frame->GetVRegReference(arg_offset + 3)->AsCharArray()));
+ DCHECK_GE(index, 0);
DCHECK_LE(index, h_char_array->GetLength());
DCHECK_LE(end - start, h_char_array->GetLength() - index);
string->GetChars(start, end, h_char_array, index);
diff --git a/runtime/jni_internal_test.cc b/runtime/jni_internal_test.cc
index e990935..a421c34 100644
--- a/runtime/jni_internal_test.cc
+++ b/runtime/jni_internal_test.cc
@@ -679,12 +679,8 @@
ASSERT_TRUE(env_->IsInstanceOf(o, c));
// ...whose fields haven't been initialized because
// we didn't call a constructor.
- if (art::mirror::kUseStringCompression) {
- // Zero-length string is compressed, so the length internally will be -(1 << 31).
- ASSERT_EQ(-2147483648, env_->GetIntField(o, env_->GetFieldID(c, "count", "I")));
- } else {
- ASSERT_EQ(0, env_->GetIntField(o, env_->GetFieldID(c, "count", "I")));
- }
+ // Even with string compression empty string has `count == 0`.
+ ASSERT_EQ(0, env_->GetIntField(o, env_->GetFieldID(c, "count", "I")));
}
TEST_F(JniInternalTest, GetVersion) {
@@ -895,11 +891,12 @@
// Make sure we can actually use it.
jstring s = env_->NewStringUTF("poop");
if (mirror::kUseStringCompression) {
- // Negative because s is compressed (first bit is 1)
- ASSERT_EQ(-2147483644, env_->GetIntField(s, fid2));
+ ASSERT_EQ(mirror::String::GetFlaggedCount(4, /* compressible */ true),
+ env_->GetIntField(s, fid2));
// Create incompressible string
jstring s_16 = env_->NewStringUTF("\u0444\u0444");
- ASSERT_EQ(2, env_->GetIntField(s_16, fid2));
+ ASSERT_EQ(mirror::String::GetFlaggedCount(2, /* compressible */ false),
+ env_->GetIntField(s_16, fid2));
} else {
ASSERT_EQ(4, env_->GetIntField(s, fid2));
}
diff --git a/runtime/mirror/string-inl.h b/runtime/mirror/string-inl.h
index d94b39f..6870fda 100644
--- a/runtime/mirror/string-inl.h
+++ b/runtime/mirror/string-inl.h
@@ -106,9 +106,7 @@
string->SetCount(count_);
const uint16_t* const src = src_array_->GetData() + offset_;
const int32_t length = String::GetLengthFromCount(count_);
- bool compressible = kUseStringCompression && String::GetCompressionFlagFromCount(count_);
- DCHECK(!compressible || kUseStringCompression);
- if (compressible) {
+ if (kUseStringCompression && String::IsCompressed(count_)) {
for (int i = 0; i < length; ++i) {
string->GetValueCompressed()[i] = static_cast<uint8_t>(src[i]);
}
@@ -126,7 +124,8 @@
// Sets string count and value in the allocation code path to ensure it is guarded by a CAS.
class SetStringCountAndValueVisitorFromString {
public:
- SetStringCountAndValueVisitorFromString(int32_t count, Handle<String> src_string,
+ SetStringCountAndValueVisitorFromString(int32_t count,
+ Handle<String> src_string,
int32_t offset) :
count_(count), src_string_(src_string), offset_(offset) {
}
@@ -137,8 +136,7 @@
ObjPtr<String> string = ObjPtr<String>::DownCast(obj);
string->SetCount(count_);
const int32_t length = String::GetLengthFromCount(count_);
- bool compressible = kUseStringCompression && String::GetCompressionFlagFromCount(count_);
- DCHECK(!compressible || kUseStringCompression);
+ bool compressible = kUseStringCompression && String::IsCompressed(count_);
if (src_string_->IsCompressed()) {
const uint8_t* const src = src_string_->GetValueCompressed() + offset_;
memcpy(string->GetValueCompressed(), src, length * sizeof(uint8_t));
@@ -209,8 +207,7 @@
gc::AllocatorType allocator_type,
const PreFenceVisitor& pre_fence_visitor) {
constexpr size_t header_size = sizeof(String);
- const bool compressible = kUseStringCompression &&
- String::GetCompressionFlagFromCount(utf16_length_with_flag);
+ const bool compressible = kUseStringCompression && String::IsCompressed(utf16_length_with_flag);
const size_t block_size = (compressible) ? sizeof(uint8_t) : sizeof(uint16_t);
size_t length = String::GetLengthFromCount(utf16_length_with_flag);
static_assert(sizeof(length) <= sizeof(size_t),
@@ -245,7 +242,7 @@
template <bool kIsInstrumented>
inline String* String::AllocEmptyString(Thread* self, gc::AllocatorType allocator_type) {
- const int32_t length_with_flag = String::GetFlaggedCount(0);
+ const int32_t length_with_flag = String::GetFlaggedCount(0, /* compressible */ true);
SetStringCountVisitor visitor(length_with_flag);
return Alloc<kIsInstrumented>(self, length_with_flag, allocator_type, visitor);
}
@@ -255,10 +252,9 @@
Handle<ByteArray> array, int32_t offset,
int32_t high_byte, gc::AllocatorType allocator_type) {
const uint8_t* const src = reinterpret_cast<uint8_t*>(array->GetData()) + offset;
- const bool compressible = kUseStringCompression && String::AllASCII<uint8_t>(src, byte_length)
- && (high_byte == 0);
- const int32_t length_with_flag = (compressible) ? String::GetFlaggedCount(byte_length)
- : byte_length;
+ const bool compressible =
+ kUseStringCompression && String::AllASCII<uint8_t>(src, byte_length) && (high_byte == 0);
+ const int32_t length_with_flag = String::GetFlaggedCount(byte_length, compressible);
SetStringCountAndBytesVisitor visitor(length_with_flag, array, offset, high_byte << 8);
String* string = Alloc<kIsInstrumented>(self, length_with_flag, allocator_type, visitor);
return string;
@@ -272,7 +268,7 @@
DCHECK_GE(array->GetLength(), count);
const bool compressible = kUseStringCompression &&
String::AllASCII<uint16_t>(array->GetData() + offset, count);
- const int32_t length_with_flag = (compressible) ? String::GetFlaggedCount(count) : count;
+ const int32_t length_with_flag = String::GetFlaggedCount(count, compressible);
SetStringCountAndValueVisitorFromCharArray visitor(length_with_flag, array, offset);
String* new_string = Alloc<kIsInstrumented>(self, length_with_flag, allocator_type, visitor);
return new_string;
@@ -284,8 +280,7 @@
const bool compressible = kUseStringCompression &&
((string->IsCompressed()) ? true : String::AllASCII<uint16_t>(string->GetValue() + offset,
string_length));
- const int32_t length_with_flag = (compressible) ? String::GetFlaggedCount(string_length)
- : string_length;
+ const int32_t length_with_flag = String::GetFlaggedCount(string_length, compressible);
SetStringCountAndValueVisitorFromString visitor(length_with_flag, string, offset);
String* new_string = Alloc<kIsInstrumented>(self, length_with_flag, allocator_type, visitor);
return new_string;
@@ -311,7 +306,7 @@
template<typename MemoryType>
bool String::AllASCII(const MemoryType* const chars, const int length) {
for (int i = 0; i < length; ++i) {
- if (chars[i] > 0x80) {
+ if (chars[i] >= 0x80) {
return false;
}
}
diff --git a/runtime/mirror/string.cc b/runtime/mirror/string.cc
index 4336aa1..0ab0bd6 100644
--- a/runtime/mirror/string.cc
+++ b/runtime/mirror/string.cc
@@ -95,8 +95,7 @@
gc::AllocatorType allocator_type = Runtime::Current()->GetHeap()->GetCurrentAllocator();
const bool compressible = kUseStringCompression &&
(string->IsCompressed() && string2->IsCompressed());
- const int32_t length_with_flag = compressible ? String::GetFlaggedCount(length + length2)
- : (length + length2);
+ const int32_t length_with_flag = String::GetFlaggedCount(length + length2, compressible);
SetStringCountVisitor visitor(length_with_flag);
ObjPtr<String> new_string = Alloc<true>(self, length_with_flag, allocator_type, visitor);
@@ -132,8 +131,7 @@
gc::AllocatorType allocator_type = Runtime::Current()->GetHeap()->GetCurrentAllocator();
const bool compressible = kUseStringCompression &&
String::AllASCII<uint16_t>(utf16_data_in, utf16_length);
- int32_t length_with_flag = (compressible) ? String::GetFlaggedCount(utf16_length)
- : utf16_length;
+ int32_t length_with_flag = String::GetFlaggedCount(utf16_length, compressible);
SetStringCountVisitor visitor(length_with_flag);
ObjPtr<String> string = Alloc<true>(self, length_with_flag, allocator_type, visitor);
if (UNLIKELY(string == nullptr)) {
@@ -169,8 +167,7 @@
int32_t utf8_length) {
gc::AllocatorType allocator_type = Runtime::Current()->GetHeap()->GetCurrentAllocator();
const bool compressible = kUseStringCompression && (utf16_length == utf8_length);
- const int32_t utf16_length_with_flag = (compressible) ? String::GetFlaggedCount(utf16_length)
- : utf16_length;
+ const int32_t utf16_length_with_flag = String::GetFlaggedCount(utf16_length, compressible);
SetStringCountVisitor visitor(utf16_length_with_flag);
ObjPtr<String> string = Alloc<true>(self, utf16_length_with_flag, allocator_type, visitor);
if (UNLIKELY(string == nullptr)) {
diff --git a/runtime/mirror/string.h b/runtime/mirror/string.h
index 6ce75bc..95b6c3e 100644
--- a/runtime/mirror/string.h
+++ b/runtime/mirror/string.h
@@ -33,6 +33,10 @@
// String Compression
static constexpr bool kUseStringCompression = false;
+enum class StringCompressionFlag : uint32_t {
+ kCompressed = 0u,
+ kUncompressed = 1u
+};
// C++ mirror of java.lang.String
class MANAGED String FINAL : public Object {
@@ -78,7 +82,6 @@
void SetCount(int32_t new_count) REQUIRES_SHARED(Locks::mutator_lock_) {
// Count is invariant so use non-transactional mode. Also disable check as we may run inside
// a transaction.
- DCHECK_LE(0, (new_count & INT32_MAX));
SetField32<false, false>(OFFSET_OF_OBJECT_MEMBER(String, count_), new_count);
}
@@ -175,7 +178,7 @@
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
bool IsCompressed() REQUIRES_SHARED(Locks::mutator_lock_) {
- return kUseStringCompression && GetCompressionFlagFromCount(GetCount());
+ return kUseStringCompression && IsCompressed(GetCount());
}
bool IsValueNull() REQUIRES_SHARED(Locks::mutator_lock_);
@@ -183,16 +186,27 @@
template<typename MemoryType>
static bool AllASCII(const MemoryType* const chars, const int length);
- ALWAYS_INLINE static bool GetCompressionFlagFromCount(const int32_t count) {
- return kUseStringCompression && ((count & (1u << 31)) != 0);
+ ALWAYS_INLINE static bool IsCompressed(int32_t count) {
+ return GetCompressionFlagFromCount(count) == StringCompressionFlag::kCompressed;
}
- ALWAYS_INLINE static int32_t GetLengthFromCount(const int32_t count) {
- return kUseStringCompression ? (count & INT32_MAX) : count;
+ ALWAYS_INLINE static StringCompressionFlag GetCompressionFlagFromCount(int32_t count) {
+ return kUseStringCompression
+ ? static_cast<StringCompressionFlag>(static_cast<uint32_t>(count) & 1u)
+ : StringCompressionFlag::kUncompressed;
}
- ALWAYS_INLINE static int32_t GetFlaggedCount(const int32_t count) {
- return kUseStringCompression ? (count | (1u << 31)) : count;
+ ALWAYS_INLINE static int32_t GetLengthFromCount(int32_t count) {
+ return kUseStringCompression ? static_cast<int32_t>(static_cast<uint32_t>(count) >> 1) : count;
+ }
+
+ ALWAYS_INLINE static int32_t GetFlaggedCount(int32_t length, bool compressible) {
+ return kUseStringCompression
+ ? static_cast<int32_t>((static_cast<uint32_t>(length) << 1) |
+ (static_cast<uint32_t>(compressible
+ ? StringCompressionFlag::kCompressed
+ : StringCompressionFlag::kUncompressed)))
+ : length;
}
static Class* GetJavaLangString() REQUIRES_SHARED(Locks::mutator_lock_) {
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index d9031ea..9d4b554 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -277,7 +277,13 @@
jobject initial_object,
const jvmtiHeapCallbacks* callbacks,
const void* user_data) {
- return ERR(NOT_IMPLEMENTED);
+ HeapUtil heap_util(&gObjectTagTable);
+ return heap_util.FollowReferences(env,
+ heap_filter,
+ klass,
+ initial_object,
+ callbacks,
+ user_data);
}
static jvmtiError IterateThroughHeap(jvmtiEnv* env,
diff --git a/runtime/openjdkjvmti/object_tagging.h b/runtime/openjdkjvmti/object_tagging.h
index 997cedb..0296f1a 100644
--- a/runtime/openjdkjvmti/object_tagging.h
+++ b/runtime/openjdkjvmti/object_tagging.h
@@ -34,7 +34,7 @@
class ObjectTagTable : public art::gc::SystemWeakHolder {
public:
explicit ObjectTagTable(EventHandler* event_handler)
- : art::gc::SystemWeakHolder(art::LockLevel::kAllocTrackerLock),
+ : art::gc::SystemWeakHolder(kTaggingLockLevel),
update_since_last_sweep_(false),
event_handler_(event_handler) {
}
@@ -180,6 +180,10 @@
}
};
+ // The tag table is used when visiting roots. So it needs to have a low lock level.
+ static constexpr art::LockLevel kTaggingLockLevel =
+ static_cast<art::LockLevel>(art::LockLevel::kAbortLock + 1);
+
std::unordered_map<art::GcRoot<art::mirror::Object>,
jlong,
HashGcRoot,
diff --git a/runtime/openjdkjvmti/ti_heap.cc b/runtime/openjdkjvmti/ti_heap.cc
index 6b20743..0eff469 100644
--- a/runtime/openjdkjvmti/ti_heap.cc
+++ b/runtime/openjdkjvmti/ti_heap.cc
@@ -16,19 +16,25 @@
#include "ti_heap.h"
+#include "art_field-inl.h"
#include "art_jvmti.h"
#include "base/macros.h"
#include "base/mutex.h"
#include "class_linker.h"
#include "gc/heap.h"
+#include "gc_root-inl.h"
#include "jni_env_ext.h"
+#include "jni_internal.h"
#include "mirror/class.h"
+#include "mirror/object-inl.h"
+#include "mirror/object_array-inl.h"
#include "object_callbacks.h"
#include "object_tagging.h"
#include "obj_ptr-inl.h"
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
#include "thread-inl.h"
+#include "thread_list.h"
namespace openjdkjvmti {
@@ -165,6 +171,466 @@
return ERR(NONE);
}
+class FollowReferencesHelper FINAL {
+ public:
+ FollowReferencesHelper(HeapUtil* h,
+ art::ObjPtr<art::mirror::Object> initial_object ATTRIBUTE_UNUSED,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data)
+ : tag_table_(h->GetTags()),
+ callbacks_(callbacks),
+ user_data_(user_data),
+ start_(0),
+ stop_reports_(false) {
+ }
+
+ void Init()
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ CollectAndReportRootsVisitor carrv(this, tag_table_, &worklist_, &visited_);
+ art::Runtime::Current()->VisitRoots(&carrv);
+ art::Runtime::Current()->VisitImageRoots(&carrv);
+ stop_reports_ = carrv.IsStopReports();
+
+ if (stop_reports_) {
+ worklist_.clear();
+ }
+ }
+
+ void Work()
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ // Currently implemented as a BFS. To lower overhead, we don't erase elements immediately
+ // from the head of the work list, instead postponing until there's a gap that's "large."
+ //
+ // Alternatively, we can implement a DFS and use the work list as a stack.
+ while (start_ < worklist_.size()) {
+ art::mirror::Object* cur_obj = worklist_[start_];
+ start_++;
+
+ if (start_ >= kMaxStart) {
+ worklist_.erase(worklist_.begin(), worklist_.begin() + start_);
+ start_ = 0;
+ }
+
+ VisitObject(cur_obj);
+
+ if (stop_reports_) {
+ break;
+ }
+ }
+ }
+
+ private:
+ class CollectAndReportRootsVisitor FINAL : public art::RootVisitor {
+ public:
+ CollectAndReportRootsVisitor(FollowReferencesHelper* helper,
+ ObjectTagTable* tag_table,
+ std::vector<art::mirror::Object*>* worklist,
+ std::unordered_set<art::mirror::Object*>* visited)
+ : helper_(helper),
+ tag_table_(tag_table),
+ worklist_(worklist),
+ visited_(visited),
+ stop_reports_(false) {}
+
+ void VisitRoots(art::mirror::Object*** roots, size_t count, const art::RootInfo& info)
+ OVERRIDE
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*helper_->tag_table_->GetAllowDisallowLock()) {
+ for (size_t i = 0; i != count; ++i) {
+ AddRoot(*roots[i], info);
+ }
+ }
+
+ void VisitRoots(art::mirror::CompressedReference<art::mirror::Object>** roots,
+ size_t count,
+ const art::RootInfo& info)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*helper_->tag_table_->GetAllowDisallowLock()) {
+ for (size_t i = 0; i != count; ++i) {
+ AddRoot(roots[i]->AsMirrorPtr(), info);
+ }
+ }
+
+ bool IsStopReports() {
+ return stop_reports_;
+ }
+
+ private:
+ void AddRoot(art::mirror::Object* root_obj, const art::RootInfo& info)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ // We use visited_ to mark roots already so we do not need another set.
+ if (visited_->find(root_obj) == visited_->end()) {
+ visited_->insert(root_obj);
+ worklist_->push_back(root_obj);
+ }
+ ReportRoot(root_obj, info);
+ }
+
+ jvmtiHeapReferenceKind GetReferenceKind(const art::RootInfo& info,
+ jvmtiHeapReferenceInfo* ref_info)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ // TODO: Fill in ref_info.
+ memset(ref_info, 0, sizeof(jvmtiHeapReferenceInfo));
+
+ switch (info.GetType()) {
+ case art::RootType::kRootJNIGlobal:
+ return JVMTI_HEAP_REFERENCE_JNI_GLOBAL;
+
+ case art::RootType::kRootJNILocal:
+ return JVMTI_HEAP_REFERENCE_JNI_LOCAL;
+
+ case art::RootType::kRootJavaFrame:
+ return JVMTI_HEAP_REFERENCE_STACK_LOCAL;
+
+ case art::RootType::kRootNativeStack:
+ case art::RootType::kRootThreadBlock:
+ case art::RootType::kRootThreadObject:
+ return JVMTI_HEAP_REFERENCE_THREAD;
+
+ case art::RootType::kRootStickyClass:
+ case art::RootType::kRootInternedString:
+ // Note: this isn't a root in the RI.
+ return JVMTI_HEAP_REFERENCE_SYSTEM_CLASS;
+
+ case art::RootType::kRootMonitorUsed:
+ case art::RootType::kRootJNIMonitor:
+ return JVMTI_HEAP_REFERENCE_MONITOR;
+
+ case art::RootType::kRootFinalizing:
+ case art::RootType::kRootDebugger:
+ case art::RootType::kRootReferenceCleanup:
+ case art::RootType::kRootVMInternal:
+ case art::RootType::kRootUnknown:
+ return JVMTI_HEAP_REFERENCE_OTHER;
+ }
+ LOG(FATAL) << "Unreachable";
+ UNREACHABLE();
+ }
+
+ void ReportRoot(art::mirror::Object* root_obj, const art::RootInfo& info)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ jvmtiHeapReferenceInfo ref_info;
+ jvmtiHeapReferenceKind kind = GetReferenceKind(info, &ref_info);
+ jint result = helper_->ReportReference(kind, &ref_info, nullptr, root_obj);
+ if ((result & JVMTI_VISIT_ABORT) != 0) {
+ stop_reports_ = true;
+ }
+ }
+
+ private:
+ FollowReferencesHelper* helper_;
+ ObjectTagTable* tag_table_;
+ std::vector<art::mirror::Object*>* worklist_;
+ std::unordered_set<art::mirror::Object*>* visited_;
+ bool stop_reports_;
+ };
+
+ void VisitObject(art::mirror::Object* obj)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ if (obj->IsClass()) {
+ VisitClass(obj->AsClass());
+ return;
+ }
+ if (obj->IsArrayInstance()) {
+ VisitArray(obj);
+ return;
+ }
+
+ // TODO: We'll probably have to rewrite this completely with our own visiting logic, if we
+ // want to have a chance of getting the field indices computed halfway efficiently. For
+ // now, ignore them altogether.
+
+ struct InstanceReferenceVisitor {
+ explicit InstanceReferenceVisitor(FollowReferencesHelper* helper_)
+ : helper(helper_), stop_reports(false) {}
+
+ void operator()(art::mirror::Object* src,
+ art::MemberOffset field_offset,
+ bool is_static ATTRIBUTE_UNUSED) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*helper->tag_table_->GetAllowDisallowLock()) {
+ if (stop_reports) {
+ return;
+ }
+
+ art::mirror::Object* trg = src->GetFieldObjectReferenceAddr(field_offset)->AsMirrorPtr();
+ jvmtiHeapReferenceInfo reference_info;
+ memset(&reference_info, 0, sizeof(reference_info));
+
+ // TODO: Implement spec-compliant numbering.
+ reference_info.field.index = field_offset.Int32Value();
+
+ jvmtiHeapReferenceKind kind =
+ field_offset.Int32Value() == art::mirror::Object::ClassOffset().Int32Value()
+ ? JVMTI_HEAP_REFERENCE_CLASS
+ : JVMTI_HEAP_REFERENCE_FIELD;
+ const jvmtiHeapReferenceInfo* reference_info_ptr =
+ kind == JVMTI_HEAP_REFERENCE_CLASS ? nullptr : &reference_info;
+
+ stop_reports = !helper->ReportReferenceMaybeEnqueue(kind, reference_info_ptr, src, trg);
+ }
+
+ void VisitRoot(art::mirror::CompressedReference<art::mirror::Object>* root ATTRIBUTE_UNUSED)
+ const {
+ LOG(FATAL) << "Unreachable";
+ }
+ void VisitRootIfNonNull(
+ art::mirror::CompressedReference<art::mirror::Object>* root ATTRIBUTE_UNUSED) const {
+ LOG(FATAL) << "Unreachable";
+ }
+
+ // "mutable" required by the visitor API.
+ mutable FollowReferencesHelper* helper;
+ mutable bool stop_reports;
+ };
+
+ InstanceReferenceVisitor visitor(this);
+ // Visit references, not native roots.
+ obj->VisitReferences<false>(visitor, art::VoidFunctor());
+
+ stop_reports_ = visitor.stop_reports;
+ }
+
+ void VisitArray(art::mirror::Object* array)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_CLASS,
+ nullptr,
+ array,
+ array->GetClass());
+ if (stop_reports_) {
+ return;
+ }
+
+ if (array->IsObjectArray()) {
+ art::mirror::ObjectArray<art::mirror::Object>* obj_array =
+ array->AsObjectArray<art::mirror::Object>();
+ int32_t length = obj_array->GetLength();
+ for (int32_t i = 0; i != length; ++i) {
+ art::mirror::Object* elem = obj_array->GetWithoutChecks(i);
+ if (elem != nullptr) {
+ jvmtiHeapReferenceInfo reference_info;
+ reference_info.array.index = i;
+ stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT,
+ &reference_info,
+ array,
+ elem);
+ if (stop_reports_) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ void VisitClass(art::mirror::Class* klass)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ // TODO: Are erroneous classes reported? Are non-prepared ones? For now, just use resolved ones.
+ if (!klass->IsResolved()) {
+ return;
+ }
+
+ // Superclass.
+ stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_SUPERCLASS,
+ nullptr,
+ klass,
+ klass->GetSuperClass());
+ if (stop_reports_) {
+ return;
+ }
+
+ // Directly implemented or extended interfaces.
+ art::Thread* self = art::Thread::Current();
+ art::StackHandleScope<1> hs(self);
+ art::Handle<art::mirror::Class> h_klass(hs.NewHandle<art::mirror::Class>(klass));
+ for (size_t i = 0; i < h_klass->NumDirectInterfaces(); ++i) {
+ art::ObjPtr<art::mirror::Class> inf_klass =
+ art::mirror::Class::GetDirectInterface(self, h_klass, i);
+ if (inf_klass == nullptr) {
+ // TODO: With a resolved class this should not happen...
+ self->ClearException();
+ break;
+ }
+
+ stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_INTERFACE,
+ nullptr,
+ klass,
+ inf_klass.Ptr());
+ if (stop_reports_) {
+ return;
+ }
+ }
+
+ // Classloader.
+ // TODO: What about the boot classpath loader? We'll skip for now, but do we have to find the
+ // fake BootClassLoader?
+ if (klass->GetClassLoader() != nullptr) {
+ stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_CLASS_LOADER,
+ nullptr,
+ klass,
+ klass->GetClassLoader());
+ if (stop_reports_) {
+ return;
+ }
+ }
+ DCHECK_EQ(h_klass.Get(), klass);
+
+ // Declared static fields.
+ for (auto& field : klass->GetSFields()) {
+ if (!field.IsPrimitiveType()) {
+ art::ObjPtr<art::mirror::Object> field_value = field.GetObject(klass);
+ if (field_value != nullptr) {
+ jvmtiHeapReferenceInfo reference_info;
+ memset(&reference_info, 0, sizeof(reference_info));
+
+ // TODO: Implement spec-compliant numbering.
+ reference_info.field.index = field.GetOffset().Int32Value();
+
+ stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_STATIC_FIELD,
+ &reference_info,
+ klass,
+ field_value.Ptr());
+ if (stop_reports_) {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ void MaybeEnqueue(art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (visited_.find(obj) == visited_.end()) {
+ worklist_.push_back(obj);
+ visited_.insert(obj);
+ }
+ }
+
+ bool ReportReferenceMaybeEnqueue(jvmtiHeapReferenceKind kind,
+ const jvmtiHeapReferenceInfo* reference_info,
+ art::mirror::Object* referree,
+ art::mirror::Object* referrer)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ jint result = ReportReference(kind, reference_info, referree, referrer);
+ if ((result & JVMTI_VISIT_ABORT) == 0) {
+ if ((result & JVMTI_VISIT_OBJECTS) != 0) {
+ MaybeEnqueue(referrer);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ jint ReportReference(jvmtiHeapReferenceKind kind,
+ const jvmtiHeapReferenceInfo* reference_info,
+ art::mirror::Object* referrer,
+ art::mirror::Object* referree)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ if (referree == nullptr || stop_reports_) {
+ return 0;
+ }
+
+ const jlong class_tag = tag_table_->GetTagOrZero(referree->GetClass());
+ const jlong referrer_class_tag =
+ referrer == nullptr ? 0 : tag_table_->GetTagOrZero(referrer->GetClass());
+ const jlong size = static_cast<jlong>(referree->SizeOf());
+ jlong tag = tag_table_->GetTagOrZero(referree);
+ jlong saved_tag = tag;
+ jlong referrer_tag = 0;
+ jlong saved_referrer_tag = 0;
+ jlong* referrer_tag_ptr;
+ if (referrer == nullptr) {
+ referrer_tag_ptr = nullptr;
+ } else {
+ if (referrer == referree) {
+ referrer_tag_ptr = &tag;
+ } else {
+ referrer_tag = saved_referrer_tag = tag_table_->GetTagOrZero(referrer);
+ referrer_tag_ptr = &referrer_tag;
+ }
+ }
+ jint length = -1;
+ if (referree->IsArrayInstance()) {
+ length = referree->AsArray()->GetLength();
+ }
+
+ jint result = callbacks_->heap_reference_callback(kind,
+ reference_info,
+ class_tag,
+ referrer_class_tag,
+ size,
+ &tag,
+ referrer_tag_ptr,
+ length,
+ const_cast<void*>(user_data_));
+
+ if (tag != saved_tag) {
+ tag_table_->Set(referree, tag);
+ }
+ if (referrer_tag != saved_referrer_tag) {
+ tag_table_->Set(referrer, referrer_tag);
+ }
+
+ return result;
+ }
+
+ ObjectTagTable* tag_table_;
+ const jvmtiHeapCallbacks* callbacks_;
+ const void* user_data_;
+
+ std::vector<art::mirror::Object*> worklist_;
+ size_t start_;
+ static constexpr size_t kMaxStart = 1000000U;
+
+ std::unordered_set<art::mirror::Object*> visited_;
+
+ bool stop_reports_;
+
+ friend class CollectAndReportRootsVisitor;
+};
+
+jvmtiError HeapUtil::FollowReferences(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jint heap_filter ATTRIBUTE_UNUSED,
+ jclass klass ATTRIBUTE_UNUSED,
+ jobject initial_object,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data) {
+ if (callbacks == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ if (callbacks->array_primitive_value_callback != nullptr) {
+ // TODO: Implement.
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self); // Now we know we have the shared lock.
+
+ art::Runtime::Current()->GetHeap()->IncrementDisableMovingGC(self);
+ {
+ art::ObjPtr<art::mirror::Object> o_initial = soa.Decode<art::mirror::Object>(initial_object);
+
+ art::ScopedThreadSuspension sts(self, art::kWaitingForVisitObjects);
+ art::ScopedSuspendAll ssa("FollowReferences");
+
+ FollowReferencesHelper frh(this, o_initial, callbacks, user_data);
+ frh.Init();
+ frh.Work();
+ }
+ art::Runtime::Current()->GetHeap()->DecrementDisableMovingGC(self);
+
+ return ERR(NONE);
+}
+
jvmtiError HeapUtil::GetLoadedClasses(jvmtiEnv* env,
jint* class_count_ptr,
jclass** classes_ptr) {
@@ -215,5 +681,4 @@
return ERR(NONE);
}
-
} // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_heap.h b/runtime/openjdkjvmti/ti_heap.h
index 570dd0c..72ee097 100644
--- a/runtime/openjdkjvmti/ti_heap.h
+++ b/runtime/openjdkjvmti/ti_heap.h
@@ -36,6 +36,13 @@
const jvmtiHeapCallbacks* callbacks,
const void* user_data);
+ jvmtiError FollowReferences(jvmtiEnv* env,
+ jint heap_filter,
+ jclass klass,
+ jobject initial_object,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data);
+
static jvmtiError ForceGarbageCollection(jvmtiEnv* env);
ObjectTagTable* GetTags() {
diff --git a/test/021-string2/src/Main.java b/test/021-string2/src/Main.java
index a848fba..51351e1 100644
--- a/test/021-string2/src/Main.java
+++ b/test/021-string2/src/Main.java
@@ -431,6 +431,22 @@
"\u0440\u0440\u0440\u0440\u0440\u0440z\u0440",
"\u0440\u0440\u0440\u0440\u0440\u0440\u0440z\u0440",
"\u0440\u0440\u0440\u0440\u0440\u0440\u0440\u0440z\u0440",
+ "\u0000",
+ "\u0000\u0000",
+ "\u0000\u0000\u0000",
+ "\u0000\u0000\u0000\u0000",
+ "\u0000\u0000\u0000\u0000\u0000",
+ "\u0000\u0000\u0000\u0000\u0000\u0000",
+ "\u0000\u0000\u0000\u0000\u0000\u0000\u0000",
+ "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000",
+ "\u0000z\u0000",
+ "\u0000\u0000z\u0000",
+ "\u0000\u0000\u0000z\u0000",
+ "\u0000\u0000\u0000\u0000z\u0000",
+ "\u0000\u0000\u0000\u0000\u0000z\u0000",
+ "\u0000\u0000\u0000\u0000\u0000\u0000z\u0000",
+ "\u0000\u0000\u0000\u0000\u0000\u0000\u0000z\u0000",
+ "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000z\u0000",
};
String[] suffixes = {
"",
@@ -458,30 +474,40 @@
String full = p + c + s;
int expX = (c.isEmpty() || c.charAt(0) != 'x') ? -1 : p.length();
int exp0440 = (c.isEmpty() || c.charAt(0) != '\u0440') ? -1 : p.length();
+ int exp0000 = (c.isEmpty() || c.charAt(0) != '\u0000') ? -1 : p.length();
Assert.assertEquals(expX, $noinline$indexOf(full, 'x'));
Assert.assertEquals(exp0440, $noinline$indexOf(full, '\u0440'));
+ Assert.assertEquals(exp0000, $noinline$indexOf(full, '\u0000'));
Assert.assertEquals(expX, $noinline$indexOf(full, 'x', -1));
Assert.assertEquals(exp0440, $noinline$indexOf(full, '\u0440', -1));
+ Assert.assertEquals(exp0000, $noinline$indexOf(full, '\u0000', -1));
Assert.assertEquals(-1, $noinline$indexOf(full, 'x', full.length() + 1));
Assert.assertEquals(-1, $noinline$indexOf(full, '\u0440', full.length() + 1));
+ Assert.assertEquals(-1, $noinline$indexOf(full, '\u0000', full.length() + 1));
for (int from = 0; from != full.length(); ++from) {
final int eX;
final int e0440;
+ final int e0000;
if (from <= p.length()) {
eX = expX;
e0440 = exp0440;
+ e0000 = exp0000;
} else if (from >= p.length() + c.length()) {
eX = -1;
e0440 = -1;
+ e0000 = -1;
} else if (full.charAt(from) == 'z') {
eX = (full.charAt(from + 1) != 'x') ? -1 : from + 1;
e0440 = (full.charAt(from + 1) != '\u0440') ? -1 : from + 1;
+ e0000 = (full.charAt(from + 1) != '\u0000') ? -1 : from + 1;
} else {
eX = (full.charAt(from) != 'x') ? -1 : from;
e0440 = (full.charAt(from) != '\u0440') ? -1 : from;
+ e0000 = (full.charAt(from) != '\u0000') ? -1 : from;
}
Assert.assertEquals(eX, $noinline$indexOf(full, 'x', from));
Assert.assertEquals(e0440, $noinline$indexOf(full, '\u0440', from));
+ Assert.assertEquals(e0000, $noinline$indexOf(full, '\u0000', from));
}
}
}
diff --git a/test/552-checker-sharpening/src/Main.java b/test/552-checker-sharpening/src/Main.java
index 95ecfb5..9e475ab 100644
--- a/test/552-checker-sharpening/src/Main.java
+++ b/test/552-checker-sharpening/src/Main.java
@@ -303,10 +303,6 @@
/// CHECK-START-MIPS: java.lang.String Main.$noinline$getNonBootImageString() sharpening (after)
/// CHECK: LoadString load_kind:BssEntry
- /// CHECK-START-MIPS: java.lang.String Main.$noinline$getNonBootImageString() pc_relative_fixups_mips (after)
- /// CHECK-DAG: MipsComputeBaseMethodAddress
- /// CHECK-DAG: LoadString load_kind:BssEntry
-
public static String $noinline$getNonBootImageString() {
// Prevent inlining to avoid the string comparison being optimized away.
if (doThrow) { throw new Error(); }
diff --git a/test/913-heaps/expected.txt b/test/913-heaps/expected.txt
index 77791a4..dc6e67d 100644
--- a/test/913-heaps/expected.txt
+++ b/test/913-heaps/expected.txt
@@ -1,2 +1,98 @@
---
true true
+root@root --(stack-local)--> 1@1000 [size=16, length=-1]
+root@root --(stack-local)--> 3000@0 [size=132, length=-1]
+root@root --(thread)--> 3000@0 [size=132, length=-1]
+1@1000 --(class)--> 1000@0 [size=123, length=-1]
+1@1000 --(field@8)--> 2@1000 [size=16, length=-1]
+1@1000 --(field@12)--> 3@1001 [size=24, length=-1]
+0@0 --(array-element@0)--> 1@1000 [size=16, length=-1]
+2@1000 --(class)--> 1000@0 [size=123, length=-1]
+3@1001 --(class)--> 1001@0 [size=123, length=-1]
+3@1001 --(field@16)--> 4@1000 [size=16, length=-1]
+3@1001 --(field@20)--> 5@1002 [size=32, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
+4@1000 --(class)--> 1000@0 [size=123, length=-1]
+5@1002 --(class)--> 1002@0 [size=123, length=-1]
+5@1002 --(field@24)--> 6@1000 [size=16, length=-1]
+5@1002 --(field@28)--> 1@1000 [size=16, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
+1002@0 --(interface)--> 2001@0 [size=132, length=-1]
+6@1000 --(class)--> 1000@0 [size=123, length=-1]
+2001@0 --(interface)--> 2000@0 [size=132, length=-1]
+---
+root@root --(stack-local)--> 1@1000 [size=16, length=-1]
+root@root --(stack-local)--> 1@1000 [size=16, length=-1]
+root@root --(stack-local)--> 1@1000 [size=16, length=-1]
+root@root --(stack-local)--> 2@1000 [size=16, length=-1]
+root@root --(stack-local)--> 3000@0 [size=132, length=-1]
+root@root --(thread)--> 2@1000 [size=16, length=-1]
+root@root --(thread)--> 3000@0 [size=132, length=-1]
+2@1000 --(class)--> 1000@0 [size=123, length=-1]
+1@1000 --(class)--> 1000@0 [size=123, length=-1]
+1@1000 --(field@8)--> 2@1000 [size=16, length=-1]
+1@1000 --(field@12)--> 3@1001 [size=24, length=-1]
+3@1001 --(class)--> 1001@0 [size=123, length=-1]
+3@1001 --(field@16)--> 4@1000 [size=16, length=-1]
+3@1001 --(field@20)--> 5@1002 [size=32, length=-1]
+0@0 --(array-element@0)--> 1@1000 [size=16, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
+4@1000 --(class)--> 1000@0 [size=123, length=-1]
+5@1002 --(class)--> 1002@0 [size=123, length=-1]
+5@1002 --(field@24)--> 6@1000 [size=16, length=-1]
+5@1002 --(field@28)--> 1@1000 [size=16, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
+1002@0 --(interface)--> 2001@0 [size=132, length=-1]
+6@1000 --(class)--> 1000@0 [size=123, length=-1]
+2001@0 --(interface)--> 2000@0 [size=132, length=-1]
+---
+root@root --(jni-global)--> 1@1000 [size=16, length=-1]
+root@root --(jni-local)--> 1@1000 [size=16, length=-1]
+root@root --(stack-local)--> 1@1000 [size=16, length=-1]
+root@root --(stack-local)--> 1@1000 [size=16, length=-1]
+root@root --(thread)--> 1@1000 [size=16, length=-1]
+root@root --(thread)--> 3000@0 [size=132, length=-1]
+1@1000 --(class)--> 1000@0 [size=123, length=-1]
+1@1000 --(field@8)--> 2@1000 [size=16, length=-1]
+1@1000 --(field@12)--> 3@1001 [size=24, length=-1]
+2@1000 --(class)--> 1000@0 [size=123, length=-1]
+3@1001 --(class)--> 1001@0 [size=123, length=-1]
+3@1001 --(field@16)--> 4@1000 [size=16, length=-1]
+3@1001 --(field@20)--> 5@1002 [size=32, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
+4@1000 --(class)--> 1000@0 [size=123, length=-1]
+5@1002 --(class)--> 1002@0 [size=123, length=-1]
+5@1002 --(field@24)--> 6@1000 [size=16, length=-1]
+5@1002 --(field@28)--> 1@1000 [size=16, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
+1002@0 --(interface)--> 2001@0 [size=132, length=-1]
+6@1000 --(class)--> 1000@0 [size=123, length=-1]
+2001@0 --(interface)--> 2000@0 [size=132, length=-1]
+---
+root@root --(jni-global)--> 1@1000 [size=16, length=-1]
+root@root --(jni-local)--> 1@1000 [size=16, length=-1]
+root@root --(stack-local)--> 1@1000 [size=16, length=-1]
+root@root --(stack-local)--> 1@1000 [size=16, length=-1]
+root@root --(stack-local)--> 1@1000 [size=16, length=-1]
+root@root --(stack-local)--> 1@1000 [size=16, length=-1]
+root@root --(stack-local)--> 2@1000 [size=16, length=-1]
+root@root --(thread)--> 1@1000 [size=16, length=-1]
+root@root --(thread)--> 2@1000 [size=16, length=-1]
+root@root --(thread)--> 3000@0 [size=132, length=-1]
+1@1000 --(class)--> 1000@0 [size=123, length=-1]
+1@1000 --(field@8)--> 2@1000 [size=16, length=-1]
+1@1000 --(field@12)--> 3@1001 [size=24, length=-1]
+2@1000 --(class)--> 1000@0 [size=123, length=-1]
+3@1001 --(class)--> 1001@0 [size=123, length=-1]
+3@1001 --(field@16)--> 4@1000 [size=16, length=-1]
+3@1001 --(field@20)--> 5@1002 [size=32, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
+4@1000 --(class)--> 1000@0 [size=123, length=-1]
+5@1002 --(class)--> 1002@0 [size=123, length=-1]
+5@1002 --(field@24)--> 6@1000 [size=16, length=-1]
+5@1002 --(field@28)--> 1@1000 [size=16, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
+1002@0 --(interface)--> 2001@0 [size=132, length=-1]
+6@1000 --(class)--> 1000@0 [size=123, length=-1]
+2001@0 --(interface)--> 2000@0 [size=132, length=-1]
+---
diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc
index 437779a..d74026c 100644
--- a/test/913-heaps/heaps.cc
+++ b/test/913-heaps/heaps.cc
@@ -16,13 +16,18 @@
#include "heaps.h"
+#include <inttypes.h>
#include <stdio.h>
#include <string.h>
+#include <vector>
+
+#include "base/logging.h"
#include "base/macros.h"
+#include "base/stringprintf.h"
#include "jni.h"
#include "openjdkjvmti/jvmti.h"
-
+#include "ti-agent/common_helper.h"
#include "ti-agent/common_load.h"
namespace art {
@@ -38,6 +43,230 @@
}
}
+class IterationConfig {
+ public:
+ IterationConfig() {}
+ virtual ~IterationConfig() {}
+
+ virtual jint Handle(jvmtiHeapReferenceKind reference_kind,
+ const jvmtiHeapReferenceInfo* reference_info,
+ jlong class_tag,
+ jlong referrer_class_tag,
+ jlong size,
+ jlong* tag_ptr,
+ jlong* referrer_tag_ptr,
+ jint length,
+ void* user_data) = 0;
+};
+
+static jint JNICALL HeapReferenceCallback(jvmtiHeapReferenceKind reference_kind,
+ const jvmtiHeapReferenceInfo* reference_info,
+ jlong class_tag,
+ jlong referrer_class_tag,
+ jlong size,
+ jlong* tag_ptr,
+ jlong* referrer_tag_ptr,
+ jint length,
+ void* user_data) {
+ IterationConfig* config = reinterpret_cast<IterationConfig*>(user_data);
+ return config->Handle(reference_kind,
+ reference_info,
+ class_tag,
+ referrer_class_tag,
+ size,
+ tag_ptr,
+ referrer_tag_ptr,
+ length,
+ user_data);
+}
+
+static bool Run(jint heap_filter,
+ jclass klass_filter,
+ jobject initial_object,
+ IterationConfig* config) {
+ jvmtiHeapCallbacks callbacks;
+ memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks));
+ callbacks.heap_reference_callback = HeapReferenceCallback;
+
+ jvmtiError ret = jvmti_env->FollowReferences(heap_filter,
+ klass_filter,
+ initial_object,
+ &callbacks,
+ config);
+ if (ret != JVMTI_ERROR_NONE) {
+ char* err;
+ jvmti_env->GetErrorName(ret, &err);
+ printf("Failure running FollowReferences: %s\n", err);
+ return false;
+ }
+ return true;
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_followReferences(JNIEnv* env,
+ jclass klass ATTRIBUTE_UNUSED,
+ jint heap_filter,
+ jclass klass_filter,
+ jobject initial_object,
+ jint stop_after,
+ jint follow_set,
+ jobject jniRef) {
+ class PrintIterationConfig FINAL : public IterationConfig {
+ public:
+ PrintIterationConfig(jint _stop_after, jint _follow_set)
+ : counter_(0),
+ stop_after_(_stop_after),
+ follow_set_(_follow_set) {
+ }
+
+ jint Handle(jvmtiHeapReferenceKind reference_kind,
+ const jvmtiHeapReferenceInfo* reference_info,
+ jlong class_tag,
+ jlong referrer_class_tag,
+ jlong size,
+ jlong* tag_ptr,
+ jlong* referrer_tag_ptr,
+ jint length,
+ void* user_data ATTRIBUTE_UNUSED) OVERRIDE {
+ jlong tag = *tag_ptr;
+ // Only check tagged objects.
+ if (tag == 0) {
+ return JVMTI_VISIT_OBJECTS;
+ }
+
+ Print(reference_kind,
+ reference_info,
+ class_tag,
+ referrer_class_tag,
+ size,
+ tag_ptr,
+ referrer_tag_ptr,
+ length);
+
+ counter_++;
+ if (counter_ == stop_after_) {
+ return JVMTI_VISIT_ABORT;
+ }
+
+ if (tag > 0 && tag < 32) {
+ bool should_visit_references = (follow_set_ & (1 << static_cast<int32_t>(tag))) != 0;
+ return should_visit_references ? JVMTI_VISIT_OBJECTS : 0;
+ }
+
+ return JVMTI_VISIT_OBJECTS;
+ }
+
+ void Print(jvmtiHeapReferenceKind reference_kind,
+ const jvmtiHeapReferenceInfo* reference_info,
+ jlong class_tag,
+ jlong referrer_class_tag,
+ jlong size,
+ jlong* tag_ptr,
+ jlong* referrer_tag_ptr,
+ jint length) {
+ std::string referrer_str;
+ if (referrer_tag_ptr == nullptr) {
+ referrer_str = "root@root";
+ } else {
+ referrer_str = StringPrintf("%" PRId64 "@%" PRId64, *referrer_tag_ptr, referrer_class_tag);
+ }
+
+ jlong adapted_size = size;
+ if (*tag_ptr >= 1000) {
+ // This is a class or interface, the size of which will be dependent on the architecture.
+ // Do not print the size, but detect known values and "normalize" for the golden file.
+ if ((sizeof(void*) == 4 && size == 180) || (sizeof(void*) == 8 && size == 232)) {
+ adapted_size = 123;
+ }
+ }
+
+ lines_.push_back(
+ StringPrintf("%s --(%s)--> %" PRId64 "@%" PRId64 " [size=%" PRId64 ", length=%d]",
+ referrer_str.c_str(),
+ GetReferenceTypeStr(reference_kind, reference_info).c_str(),
+ *tag_ptr,
+ class_tag,
+ adapted_size,
+ length));
+ }
+
+ static std::string GetReferenceTypeStr(jvmtiHeapReferenceKind reference_kind,
+ const jvmtiHeapReferenceInfo* reference_info) {
+ switch (reference_kind) {
+ case JVMTI_HEAP_REFERENCE_CLASS:
+ return "class";
+ case JVMTI_HEAP_REFERENCE_FIELD:
+ return StringPrintf("field@%d", reference_info->field.index);
+ case JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT:
+ return StringPrintf("array-element@%d", reference_info->array.index);
+ case JVMTI_HEAP_REFERENCE_CLASS_LOADER:
+ return "classloader";
+ case JVMTI_HEAP_REFERENCE_SIGNERS:
+ return "signers";
+ case JVMTI_HEAP_REFERENCE_PROTECTION_DOMAIN:
+ return "protection-domain";
+ case JVMTI_HEAP_REFERENCE_INTERFACE:
+ return "interface";
+ case JVMTI_HEAP_REFERENCE_STATIC_FIELD:
+ return StringPrintf("static-field@%d", reference_info->field.index);
+ case JVMTI_HEAP_REFERENCE_CONSTANT_POOL:
+ return "constant-pool";
+ case JVMTI_HEAP_REFERENCE_SUPERCLASS:
+ return "superclass";
+ case JVMTI_HEAP_REFERENCE_JNI_GLOBAL:
+ return "jni-global";
+ case JVMTI_HEAP_REFERENCE_SYSTEM_CLASS:
+ return "system-class";
+ case JVMTI_HEAP_REFERENCE_MONITOR:
+ return "monitor";
+ case JVMTI_HEAP_REFERENCE_STACK_LOCAL:
+ return "stack-local";
+ case JVMTI_HEAP_REFERENCE_JNI_LOCAL:
+ return "jni-local";
+ case JVMTI_HEAP_REFERENCE_THREAD:
+ return "thread";
+ case JVMTI_HEAP_REFERENCE_OTHER:
+ return "other";
+ }
+ return "unknown";
+ }
+
+ const std::vector<std::string>& GetLines() const {
+ return lines_;
+ }
+
+ private:
+ jint counter_;
+ const jint stop_after_;
+ const jint follow_set_;
+ std::vector<std::string> lines_;
+ };
+
+ // If jniRef isn't null, add a local and a global ref.
+ ScopedLocalRef<jobject> jni_local_ref(env, nullptr);
+ jobject jni_global_ref = nullptr;
+ if (jniRef != nullptr) {
+ jni_local_ref.reset(env->NewLocalRef(jniRef));
+ jni_global_ref = env->NewGlobalRef(jniRef);
+ }
+
+ PrintIterationConfig config(stop_after, follow_set);
+ Run(heap_filter, klass_filter, initial_object, &config);
+
+ const std::vector<std::string>& lines = config.GetLines();
+ jobjectArray ret = CreateObjectArray(env,
+ static_cast<jint>(lines.size()),
+ "java/lang/String",
+ [&](jint i) {
+ return env->NewStringUTF(lines[i].c_str());
+ });
+
+ if (jni_global_ref != nullptr) {
+ env->DeleteGlobalRef(jni_global_ref);
+ }
+
+ return ret;
+}
+
// Don't do anything
jint OnLoad(JavaVM* vm,
char* options ATTRIBUTE_UNUSED,
diff --git a/test/913-heaps/src/Main.java b/test/913-heaps/src/Main.java
index 4d77a48..f463429 100644
--- a/test/913-heaps/src/Main.java
+++ b/test/913-heaps/src/Main.java
@@ -15,12 +15,14 @@
*/
import java.util.ArrayList;
+import java.util.Collections;
public class Main {
public static void main(String[] args) throws Exception {
System.loadLibrary(args[1]);
doTest();
+ doFollowReferencesTest();
}
public static void doTest() throws Exception {
@@ -43,10 +45,161 @@
}
private static void printStats() {
- System.out.println("---");
- int s = getGcStarts();
- int f = getGcFinishes();
- System.out.println((s > 0) + " " + (f > 0));
+ System.out.println("---");
+ int s = getGcStarts();
+ int f = getGcFinishes();
+ System.out.println((s > 0) + " " + (f > 0));
+ }
+
+ public static void doFollowReferencesTest() throws Exception {
+ // Force GCs to clean up dirt.
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().gc();
+
+ tagClasses();
+ setTag(Thread.currentThread(), 3000);
+
+ {
+ ArrayList<Object> tmpStorage = new ArrayList<>();
+ doFollowReferencesTestNonRoot(tmpStorage);
+ tmpStorage = null;
+ }
+
+ // Force GCs to clean up dirt.
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().gc();
+
+ doFollowReferencesTestRoot();
+
+ // Force GCs to clean up dirt.
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().gc();
+ }
+
+ private static void doFollowReferencesTestNonRoot(ArrayList<Object> tmpStorage) {
+ A a = createTree();
+ tmpStorage.add(a);
+ doFollowReferencesTestImpl(null, Integer.MAX_VALUE, -1, null);
+ doFollowReferencesTestImpl(a, Integer.MAX_VALUE, -1, null);
+ tmpStorage.clear();
+ }
+
+ private static void doFollowReferencesTestRoot() {
+ A a = createTree();
+ doFollowReferencesTestImpl(null, Integer.MAX_VALUE, -1, a);
+ doFollowReferencesTestImpl(a, Integer.MAX_VALUE, -1, a);
+ }
+
+ private static void doFollowReferencesTestImpl(A root, int stopAfter, int followSet,
+ Object asRoot) {
+ String[] lines =
+ followReferences(0, null, root == null ? null : root.foo, stopAfter, followSet, asRoot);
+ // Note: sort the roots, as stack locals visit order isn't defined, so may depend on compiled
+ // code. Do not sort non-roots, as the order here needs to be verified (elements are
+ // finished before a reference is followed). The test setup (and root visit order)
+ // luckily ensures that this is deterministic.
+
+ int i = 0;
+ ArrayList<String> rootLines = new ArrayList<>();
+ while (i < lines.length) {
+ if (lines[i].startsWith("root")) {
+ rootLines.add(lines[i]);
+ } else {
+ break;
+ }
+ i++;
+ }
+ Collections.sort(rootLines);
+ for (String l : rootLines) {
+ System.out.println(l);
+ }
+
+ // Print the non-root lines in order.
+ while (i < lines.length) {
+ System.out.println(lines[i]);
+ i++;
+ }
+
+ System.out.println("---");
+
+ // TODO: Test filters.
+ }
+
+ private static void tagClasses() {
+ setTag(A.class, 1000);
+ setTag(B.class, 1001);
+ setTag(C.class, 1002);
+ setTag(I1.class, 2000);
+ setTag(I2.class, 2001);
+ }
+
+ private static A createTree() {
+ A root = new A();
+ setTag(root, 1);
+
+ A foo = new A();
+ setTag(foo, 2);
+ root.foo = foo;
+
+ B foo2 = new B();
+ setTag(foo2, 3);
+ root.foo2 = foo2;
+
+ A bar = new A();
+ setTag(bar, 4);
+ foo2.bar = bar;
+
+ C bar2 = new C();
+ setTag(bar2, 5);
+ foo2.bar2 = bar2;
+
+ A baz = new A();
+ setTag(baz, 6);
+ bar2.baz = baz;
+ bar2.baz2 = root;
+
+ return root;
+ }
+
+ public static class A {
+ public A foo;
+ public A foo2;
+
+ public A() {}
+ public A(A a, A b) {
+ foo = a;
+ foo2 = b;
+ }
+ }
+
+ public static class B extends A {
+ public A bar;
+ public A bar2;
+
+ public B() {}
+ public B(A a, A b) {
+ bar = a;
+ bar2 = b;
+ }
+ }
+
+ public static interface I1 {
+ public final static int i1Field = 1;
+ }
+
+ public static interface I2 extends I1 {
+ public final static int i2Field = 2;
+ }
+
+ public static class C extends B implements I2 {
+ public A baz;
+ public A baz2;
+
+ public C() {}
+ public C(A a, A b) {
+ baz = a;
+ baz2 = b;
+ }
}
private static native void setupGcCallback();
@@ -54,4 +207,10 @@
private static native int getGcStarts();
private static native int getGcFinishes();
private static native void forceGarbageCollection();
+
+ private static native void setTag(Object o, long tag);
+ private static native long getTag(Object o);
+
+ private static native String[] followReferences(int heapFilter, Class<?> klassFilter,
+ Object initialObject, int stopAfter, int followSet, Object jniRef);
}
diff --git a/test/957-methodhandle-transforms/src/Main.java b/test/957-methodhandle-transforms/src/Main.java
index 3c6f119..5806509 100644
--- a/test/957-methodhandle-transforms/src/Main.java
+++ b/test/957-methodhandle-transforms/src/Main.java
@@ -31,6 +31,8 @@
testIdentity();
testConstant();
testBindTo();
+ testFilterReturnValue();
+ testPermuteArguments();
}
public static void testThrowException() throws Throwable {
@@ -708,6 +710,184 @@
}
}
+ public static String filterReturnValue_target(int a) {
+ return "ReturnValue" + a;
+ }
+
+ public static boolean filterReturnValue_filter(String value) {
+ return value.indexOf("42") != -1;
+ }
+
+ public static int filterReturnValue_intTarget(String a) {
+ return Integer.parseInt(a);
+ }
+
+ public static int filterReturnValue_intFilter(int b) {
+ return b + 1;
+ }
+
+ public static void filterReturnValue_voidTarget() {
+ }
+
+ public static int filterReturnValue_voidFilter() {
+ return 42;
+ }
+
+ public static void testFilterReturnValue() throws Throwable {
+ // A target that returns a reference.
+ {
+ final MethodHandle target = MethodHandles.lookup().findStatic(Main.class,
+ "filterReturnValue_target", MethodType.methodType(String.class, int.class));
+ final MethodHandle filter = MethodHandles.lookup().findStatic(Main.class,
+ "filterReturnValue_filter", MethodType.methodType(boolean.class, String.class));
+
+ MethodHandle adapter = MethodHandles.filterReturnValue(target, filter);
+
+ boolean value = (boolean) adapter.invoke((int) 42);
+ if (!value) {
+ System.out.println("Unexpected value: " + value);
+ }
+ value = (boolean) adapter.invoke((int) 43);
+ if (value) {
+ System.out.println("Unexpected value: " + value);
+ }
+ }
+
+ // A target that returns a primitive.
+ {
+ final MethodHandle target = MethodHandles.lookup().findStatic(Main.class,
+ "filterReturnValue_intTarget", MethodType.methodType(int.class, String.class));
+ final MethodHandle filter = MethodHandles.lookup().findStatic(Main.class,
+ "filterReturnValue_intFilter", MethodType.methodType(int.class, int.class));
+
+ MethodHandle adapter = MethodHandles.filterReturnValue(target, filter);
+
+ int value = (int) adapter.invoke("56");
+ if (value != 57) {
+ System.out.println("Unexpected value: " + value);
+ }
+ }
+
+ // A target that returns void.
+ {
+ final MethodHandle target = MethodHandles.lookup().findStatic(Main.class,
+ "filterReturnValue_voidTarget", MethodType.methodType(void.class));
+ final MethodHandle filter = MethodHandles.lookup().findStatic(Main.class,
+ "filterReturnValue_voidFilter", MethodType.methodType(int.class));
+
+ MethodHandle adapter = MethodHandles.filterReturnValue(target, filter);
+
+ int value = (int) adapter.invoke();
+ if (value != 42) {
+ System.out.println("Unexpected value: " + value);
+ }
+ }
+ }
+
+ public static void permuteArguments_callee(boolean a, byte b, char c,
+ short d, int e, long f, float g, double h) {
+ if (a == true && b == (byte) 'b' && c == 'c' && d == (short) 56 &&
+ e == 78 && f == (long) 97 && g == 98.0f && f == 97.0) {
+ return;
+ }
+
+ System.out.println("Unexpected arguments: " + a + ", " + b + ", " + c
+ + ", " + d + ", " + e + ", " + f + ", " + g + ", " + h);
+ }
+
+ public static void permuteArguments_boxingCallee(boolean a, Integer b) {
+ if (a && b.intValue() == 42) {
+ return;
+ }
+
+ System.out.println("Unexpected arguments: " + a + ", " + b);
+ }
+
+ public static void testPermuteArguments() throws Throwable {
+ {
+ final MethodHandle target = MethodHandles.lookup().findStatic(
+ Main.class, "permuteArguments_callee",
+ MethodType.methodType(void.class, new Class<?>[] {
+ boolean.class, byte.class, char.class, short.class, int.class,
+ long.class, float.class, double.class }));
+
+ final MethodType newType = MethodType.methodType(void.class, new Class<?>[] {
+ double.class, float.class, long.class, int.class, short.class, char.class,
+ byte.class, boolean.class });
+
+ final MethodHandle permutation = MethodHandles.permuteArguments(
+ target, newType, new int[] { 7, 6, 5, 4, 3, 2, 1, 0 });
+
+ permutation.invoke((double) 97.0, (float) 98.0f, (long) 97, 78,
+ (short) 56, 'c', (byte) 'b', (boolean) true);
+
+ // The permutation array was not of the right length.
+ try {
+ MethodHandles.permuteArguments(target, newType,
+ new int[] { 7 });
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // The permutation array has an element that's out of bounds
+ // (there's no argument with idx == 8).
+ try {
+ MethodHandles.permuteArguments(target, newType,
+ new int[] { 8, 6, 5, 4, 3, 2, 1, 0 });
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // The permutation array maps to an incorrect type.
+ try {
+ MethodHandles.permuteArguments(target, newType,
+ new int[] { 7, 7, 5, 4, 3, 2, 1, 0 });
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ // Tests for reference arguments as well as permutations that
+ // repeat arguments.
+ {
+ final MethodHandle target = MethodHandles.lookup().findVirtual(
+ String.class, "concat", MethodType.methodType(String.class, String.class));
+
+ final MethodType newType = MethodType.methodType(String.class, String.class,
+ String.class);
+
+ assertEquals("foobar", (String) target.invoke("foo", "bar"));
+
+ MethodHandle permutation = MethodHandles.permuteArguments(target,
+ newType, new int[] { 1, 0 });
+ assertEquals("barfoo", (String) permutation.invoke("foo", "bar"));
+
+ permutation = MethodHandles.permuteArguments(target, newType, new int[] { 0, 0 });
+ assertEquals("foofoo", (String) permutation.invoke("foo", "bar"));
+
+ permutation = MethodHandles.permuteArguments(target, newType, new int[] { 1, 1 });
+ assertEquals("barbar", (String) permutation.invoke("foo", "bar"));
+ }
+
+ // Tests for boxing and unboxing.
+ {
+ final MethodHandle target = MethodHandles.lookup().findStatic(
+ Main.class, "permuteArguments_boxingCallee",
+ MethodType.methodType(void.class, new Class<?>[] { boolean.class, Integer.class }));
+
+ final MethodType newType = MethodType.methodType(void.class,
+ new Class<?>[] { Integer.class, boolean.class });
+
+ MethodHandle permutation = MethodHandles.permuteArguments(target,
+ newType, new int[] { 1, 0 });
+
+ permutation.invoke(42, true);
+ permutation.invoke(42, Boolean.TRUE);
+ permutation.invoke(Integer.valueOf(42), true);
+ permutation.invoke(Integer.valueOf(42), Boolean.TRUE);
+ }
+ }
+
public static void fail() {
System.out.println("FAIL");
Thread.dumpStack();
@@ -725,5 +905,3 @@
throw new AssertionError("assertEquals s1: " + s1 + ", s2: " + s2);
}
}
-
-
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index 8f8f998..29cec91 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -230,6 +230,40 @@
$(COMPILER_TYPES),$(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES), \
$(IMAGE_TYPES), $(PICTEST_TYPES), $(DEBUGGABLE_TYPES), $(ART_TEST_RUN_TEST_SKIP), $(ALL_ADDRESS_SIZES))
+# b/31385354: Roots (and thus iteration order) is non-stable between different run modes.
+# Temporarily disable test for everything but default optimizing configuration
+# until the test check code is generalized to allow spec-compliant output.
+TEST_ART_BROKEN_B31385354_TESTS := \
+ 913-heaps \
+
+NON_AOT_MODES := $(filter-out optimizing,$(COMPILER_TYPES))
+ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \
+ $(NON_AOT_MODES), $(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES), \
+ $(IMAGE_TYPES),$(PICTEST_TYPES),$(DEBUGGABLE_TYPES), $(TEST_ART_BROKEN_B31385354_TESTS), \
+ $(ALL_ADDRESS_SIZES))
+NON_AOT_MODES :=
+
+NON_PREBUILD_MODES := $(filter-out prebuild,$(PREBUILD_TYPES))
+ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(NON_PREBUILD_MODES), \
+ $(COMPILER_TYPES), $(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES), \
+ $(IMAGE_TYPES),$(PICTEST_TYPES),$(DEBUGGABLE_TYPES), $(TEST_ART_BROKEN_B31385354_TESTS), \
+ $(ALL_ADDRESS_SIZES))
+NON_PREBUILD_MODES :=
+
+NON_RELOCATE_MODES := $(filter-out relocate,$(RELOCATE_TYPES))
+ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \
+ $(COMPILER_TYPES), $(NON_RELOCATE_MODES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES), \
+ $(IMAGE_TYPES),$(PICTEST_TYPES),$(DEBUGGABLE_TYPES), $(TEST_ART_BROKEN_B31385354_TESTS), \
+ $(ALL_ADDRESS_SIZES))
+NON_RELOCATE_MODES :=
+
+ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \
+ $(COMPILER_TYPES), $(RELOCATE_TYPES),trace,$(GC_TYPES),$(JNI_TYPES), \
+ $(IMAGE_TYPES),$(PICTEST_TYPES),$(DEBUGGABLE_TYPES), $(TEST_ART_BROKEN_B31385354_TESTS), \
+ $(ALL_ADDRESS_SIZES))
+
+TEST_ART_BROKEN_B31385354_TESTS :=
+
# Disable 149-suspend-all-stress, its output is flaky (b/28988206).
# Disable 577-profile-foreign-dex (b/27454772).