Merge "Don't leak oat file when we fail to open a dex file."
diff --git a/Android.mk b/Android.mk
index 4351be9..92339af 100644
--- a/Android.mk
+++ b/Android.mk
@@ -46,9 +46,6 @@
rm -f $(ART_TEST_OUT)/*.odex
rm -f $(ART_TEST_OUT)/*.oat
rm -f $(ART_TEST_OUT)/*.art
- rm -f $(DALVIK_CACHE_OUT)/*@classes.dex
- rm -f $(DALVIK_CACHE_OUT)/*.oat
- rm -f $(DALVIK_CACHE_OUT)/*.art
rm -f $(HOST_OUT_JAVA_LIBRARIES)/*.odex
rm -f $(HOST_OUT_JAVA_LIBRARIES)/*.oat
rm -f $(HOST_OUT_JAVA_LIBRARIES)/*.art
@@ -66,25 +63,31 @@
rm -f $(2ND_TARGET_OUT_INTERMEDIATES)/JAVA_LIBRARIES/*_intermediates/javalib.odex
rm -f $(2ND_TARGET_OUT_INTERMEDIATES)/APPS/*_intermediates/*.odex
endif
- rm -rf /tmp/test-*/dalvik-cache/*@classes.dex
- rm -rf /tmp/android-data/dalvik-cache/*@classes.dex
+ rm -rf /tmp/test-*/dalvik-cache/*
+ rm -rf /tmp/android-data/dalvik-cache/*
.PHONY: clean-oat-target
clean-oat-target:
adb remount
- adb shell rm $(ART_NATIVETEST_DIR)/*.odex
- adb shell rm $(ART_NATIVETEST_DIR)/*.oat
- adb shell rm $(ART_NATIVETEST_DIR)/*.art
- adb shell rm $(ART_TEST_DIR)/*.odex
- adb shell rm $(ART_TEST_DIR)/*.oat
- adb shell rm $(ART_TEST_DIR)/*.art
- adb shell rm $(ART_DALVIK_CACHE_DIR)/*.dex
- adb shell rm $(ART_DALVIK_CACHE_DIR)/*.oat
- adb shell rm $(ART_DALVIK_CACHE_DIR)/*.art
- adb shell rm $(DEXPREOPT_BOOT_JAR_DIR)/*.oat
- adb shell rm $(DEXPREOPT_BOOT_JAR_DIR)/*.art
- adb shell rm system/app/*.odex
- adb shell rm data/run-test/test-*/dalvik-cache/*@classes.dex
+ adb shell rm -f $(ART_NATIVETEST_DIR)/*.odex
+ adb shell rm -f $(ART_NATIVETEST_DIR)/*.oat
+ adb shell rm -f $(ART_NATIVETEST_DIR)/*.art
+ adb shell rm -f $(ART_TEST_DIR)/*.odex
+ adb shell rm -f $(ART_TEST_DIR)/*.oat
+ adb shell rm -f $(ART_TEST_DIR)/*.art
+ifdef TARGET_2ND_ARCH
+ adb shell rm -f $(2ND_ART_NATIVETEST_DIR)/*.odex
+ adb shell rm -f $(2ND_ART_NATIVETEST_DIR)/*.oat
+ adb shell rm -f $(2ND_ART_NATIVETEST_DIR)/*.art
+ adb shell rm -f $(2ND_ART_TEST_DIR)/*.odex
+ adb shell rm -f $(2ND_ART_TEST_DIR)/*.oat
+ adb shell rm -f $(2ND_ART_TEST_DIR)/*.art
+endif
+ adb shell rm -rf $(ART_DALVIK_CACHE_DIR)/*
+ adb shell rm -f $(DEXPREOPT_BOOT_JAR_DIR)/*.oat
+ adb shell rm -f $(DEXPREOPT_BOOT_JAR_DIR)/*.art
+ adb shell rm -f system/app/*.odex
+ adb shell rm -rf data/run-test/test-*/dalvik-cache/*
ifneq ($(art_dont_bother),true)
diff --git a/compiler/dex/quick/codegen_util.cc b/compiler/dex/quick/codegen_util.cc
index c3f9b67..b030bb4 100644
--- a/compiler/dex/quick/codegen_util.cc
+++ b/compiler/dex/quick/codegen_util.cc
@@ -555,8 +555,8 @@
return offset;
}
-static int AssignLiteralPointerOffsetCommon(LIR* lir, CodeOffset offset) {
- unsigned int element_size = sizeof(void*);
+static int AssignLiteralPointerOffsetCommon(LIR* lir, CodeOffset offset,
+ unsigned int element_size) {
// Align to natural pointer size.
offset = (offset + (element_size - 1)) & ~(element_size - 1);
for (; lir != NULL; lir = lir->next) {
@@ -726,9 +726,10 @@
/* Determine the offset of each literal field */
int Mir2Lir::AssignLiteralOffset(CodeOffset offset) {
offset = AssignLiteralOffsetCommon(literal_list_, offset);
- offset = AssignLiteralPointerOffsetCommon(code_literal_list_, offset);
- offset = AssignLiteralPointerOffsetCommon(method_literal_list_, offset);
- offset = AssignLiteralPointerOffsetCommon(class_literal_list_, offset);
+ unsigned int ptr_size = GetInstructionSetPointerSize(cu_->instruction_set);
+ offset = AssignLiteralPointerOffsetCommon(code_literal_list_, offset, ptr_size);
+ offset = AssignLiteralPointerOffsetCommon(method_literal_list_, offset, ptr_size);
+ offset = AssignLiteralPointerOffsetCommon(class_literal_list_, offset, ptr_size);
return offset;
}
diff --git a/compiler/dex/quick/gen_invoke.cc b/compiler/dex/quick/gen_invoke.cc
index 05313a9..93a23a6 100644
--- a/compiler/dex/quick/gen_invoke.cc
+++ b/compiler/dex/quick/gen_invoke.cc
@@ -455,14 +455,14 @@
if (direct_code != 0 && direct_method != 0) {
switch (state) {
case 0: // Get the current Method* [sets kArg0]
- if (direct_code != static_cast<unsigned int>(-1)) {
+ if (direct_code != static_cast<uintptr_t>(-1)) {
if (cu->instruction_set != kX86 && cu->instruction_set != kX86_64) {
cg->LoadConstant(cg->TargetReg(kInvokeTgt), direct_code);
}
} else if (cu->instruction_set != kX86 && cu->instruction_set != kX86_64) {
cg->LoadCodeAddress(target_method, type, kInvokeTgt);
}
- if (direct_method != static_cast<unsigned int>(-1)) {
+ if (direct_method != static_cast<uintptr_t>(-1)) {
cg->LoadConstant(cg->TargetReg(kArg0), direct_method);
} else {
cg->LoadMethodAddress(target_method, type, kArg0);
@@ -483,7 +483,7 @@
cg->TargetReg(kArg0));
// Set up direct code if known.
if (direct_code != 0) {
- if (direct_code != static_cast<unsigned int>(-1)) {
+ if (direct_code != static_cast<uintptr_t>(-1)) {
cg->LoadConstant(cg->TargetReg(kInvokeTgt), direct_code);
} else if (cu->instruction_set != kX86 && cu->instruction_set != kX86_64) {
CHECK_LT(target_method.dex_method_index, target_method.dex_file->NumMethodIds());
diff --git a/compiler/dex/quick/mir_to_lir.cc b/compiler/dex/quick/mir_to_lir.cc
index df7a7c1..6c5279e 100644
--- a/compiler/dex/quick/mir_to_lir.cc
+++ b/compiler/dex/quick/mir_to_lir.cc
@@ -42,7 +42,7 @@
RegStorage reg_arg_high = wide ? GetArgMappingToPhysicalReg(in_position + 1) :
RegStorage::InvalidReg();
- int offset = StackVisitor::GetOutVROffset(in_position);
+ int offset = StackVisitor::GetOutVROffset(in_position, cu_->instruction_set);
if (cu_->instruction_set == kX86 || cu_->instruction_set == kX86_64) {
/*
* When doing a call for x86, it moves the stack pointer in order to push return.
@@ -81,7 +81,7 @@
}
void Mir2Lir::LoadArgDirect(int in_position, RegLocation rl_dest) {
- int offset = StackVisitor::GetOutVROffset(in_position);
+ int offset = StackVisitor::GetOutVROffset(in_position, cu_->instruction_set);
if (cu_->instruction_set == kX86 || cu_->instruction_set == kX86_64) {
/*
* When doing a call for x86, it moves the stack pointer in order to push return.
diff --git a/compiler/dex/quick/ralloc_util.cc b/compiler/dex/quick/ralloc_util.cc
index 39783a2..6455572 100644
--- a/compiler/dex/quick/ralloc_util.cc
+++ b/compiler/dex/quick/ralloc_util.cc
@@ -1141,7 +1141,8 @@
/* Returns sp-relative offset in bytes for a VReg */
int Mir2Lir::VRegOffset(int v_reg) {
return StackVisitor::GetVRegOffset(cu_->code_item, core_spill_mask_,
- fp_spill_mask_, frame_size_, v_reg);
+ fp_spill_mask_, frame_size_, v_reg,
+ cu_->instruction_set);
}
/* Returns sp-relative offset in bytes for a SReg */
diff --git a/compiler/jni/quick/arm/calling_convention_arm.h b/compiler/jni/quick/arm/calling_convention_arm.h
index 00a239b..604ce1c 100644
--- a/compiler/jni/quick/arm/calling_convention_arm.h
+++ b/compiler/jni/quick/arm/calling_convention_arm.h
@@ -71,6 +71,11 @@
ManagedRegister CurrentParamRegister() OVERRIDE;
FrameOffset CurrentParamStackOffset() OVERRIDE;
+ // AAPCS mandates return values are extended.
+ bool RequiresSmallResultTypeExtension() const OVERRIDE {
+ return false;
+ }
+
protected:
size_t NumberOfOutgoingStackArgs() OVERRIDE;
diff --git a/compiler/jni/quick/arm64/calling_convention_arm64.h b/compiler/jni/quick/arm64/calling_convention_arm64.h
index 92f547c..9fd3265 100644
--- a/compiler/jni/quick/arm64/calling_convention_arm64.h
+++ b/compiler/jni/quick/arm64/calling_convention_arm64.h
@@ -68,6 +68,11 @@
ManagedRegister CurrentParamRegister() OVERRIDE;
FrameOffset CurrentParamStackOffset() OVERRIDE;
+ // aarch64 calling convention leaves upper bits undefined.
+ bool RequiresSmallResultTypeExtension() const OVERRIDE {
+ return true;
+ }
+
protected:
size_t NumberOfOutgoingStackArgs() OVERRIDE;
diff --git a/compiler/jni/quick/calling_convention.h b/compiler/jni/quick/calling_convention.h
index 4d25d1c..18afd58 100644
--- a/compiler/jni/quick/calling_convention.h
+++ b/compiler/jni/quick/calling_convention.h
@@ -287,6 +287,8 @@
FrameOffset ReturnValueSaveLocation() const;
// Register that holds result if it is integer.
virtual ManagedRegister IntReturnRegister() = 0;
+ // Whether the compiler needs to ensure zero-/sign-extension of a small result type
+ virtual bool RequiresSmallResultTypeExtension() const = 0;
// Callee save registers to spill prior to native code (which may clobber)
virtual const std::vector<ManagedRegister>& CalleeSaveRegisters() const = 0;
diff --git a/compiler/jni/quick/jni_compiler.cc b/compiler/jni/quick/jni_compiler.cc
index 93b1b5a..9f439eb 100644
--- a/compiler/jni/quick/jni_compiler.cc
+++ b/compiler/jni/quick/jni_compiler.cc
@@ -314,7 +314,7 @@
mr_conv->InterproceduralScratchRegister());
// 10. Fix differences in result widths.
- if (instruction_set == kX86 || instruction_set == kX86_64) {
+ if (main_jni_conv->RequiresSmallResultTypeExtension()) {
if (main_jni_conv->GetReturnType() == Primitive::kPrimByte ||
main_jni_conv->GetReturnType() == Primitive::kPrimShort) {
__ SignExtend(main_jni_conv->ReturnRegister(),
diff --git a/compiler/jni/quick/mips/calling_convention_mips.h b/compiler/jni/quick/mips/calling_convention_mips.h
index e33fbad..8d82dce 100644
--- a/compiler/jni/quick/mips/calling_convention_mips.h
+++ b/compiler/jni/quick/mips/calling_convention_mips.h
@@ -71,6 +71,11 @@
ManagedRegister CurrentParamRegister() OVERRIDE;
FrameOffset CurrentParamStackOffset() OVERRIDE;
+ // Mips does not need to extend small return types.
+ bool RequiresSmallResultTypeExtension() const OVERRIDE {
+ return false;
+ }
+
protected:
size_t NumberOfOutgoingStackArgs() OVERRIDE;
diff --git a/compiler/jni/quick/x86/calling_convention_x86.h b/compiler/jni/quick/x86/calling_convention_x86.h
index 5b9069c..025eb6d 100644
--- a/compiler/jni/quick/x86/calling_convention_x86.h
+++ b/compiler/jni/quick/x86/calling_convention_x86.h
@@ -69,6 +69,11 @@
ManagedRegister CurrentParamRegister() OVERRIDE;
FrameOffset CurrentParamStackOffset() OVERRIDE;
+ // x86 needs to extend small return types.
+ bool RequiresSmallResultTypeExtension() const OVERRIDE {
+ return true;
+ }
+
protected:
size_t NumberOfOutgoingStackArgs() OVERRIDE;
diff --git a/compiler/jni/quick/x86_64/calling_convention_x86_64.h b/compiler/jni/quick/x86_64/calling_convention_x86_64.h
index d545774..1ba5353 100644
--- a/compiler/jni/quick/x86_64/calling_convention_x86_64.h
+++ b/compiler/jni/quick/x86_64/calling_convention_x86_64.h
@@ -69,6 +69,11 @@
ManagedRegister CurrentParamRegister() OVERRIDE;
FrameOffset CurrentParamStackOffset() OVERRIDE;
+ // x86-64 needs to extend small return types.
+ bool RequiresSmallResultTypeExtension() const OVERRIDE {
+ return true;
+ }
+
protected:
size_t NumberOfOutgoingStackArgs() OVERRIDE;
diff --git a/compiler/utils/arm64/assembler_arm64.cc b/compiler/utils/arm64/assembler_arm64.cc
index 1d87eaa..b4bb979 100644
--- a/compiler/utils/arm64/assembler_arm64.cc
+++ b/compiler/utils/arm64/assembler_arm64.cc
@@ -467,12 +467,26 @@
#endif
}
-void Arm64Assembler::SignExtend(ManagedRegister /*mreg*/, size_t /*size*/) {
- UNIMPLEMENTED(FATAL) << "no sign extension necessary for Arm64";
+void Arm64Assembler::SignExtend(ManagedRegister mreg, size_t size) {
+ Arm64ManagedRegister reg = mreg.AsArm64();
+ CHECK(size == 1 || size == 2) << size;
+ CHECK(reg.IsWRegister()) << reg;
+ if (size == 1) {
+ ___ sxtb(reg_w(reg.AsWRegister()), reg_w(reg.AsWRegister()));
+ } else {
+ ___ sxth(reg_w(reg.AsWRegister()), reg_w(reg.AsWRegister()));
+ }
}
-void Arm64Assembler::ZeroExtend(ManagedRegister /*mreg*/, size_t /*size*/) {
- UNIMPLEMENTED(FATAL) << "no zero extension necessary for Arm64";
+void Arm64Assembler::ZeroExtend(ManagedRegister mreg, size_t size) {
+ Arm64ManagedRegister reg = mreg.AsArm64();
+ CHECK(size == 1 || size == 2) << size;
+ CHECK(reg.IsWRegister()) << reg;
+ if (size == 1) {
+ ___ uxtb(reg_w(reg.AsWRegister()), reg_w(reg.AsWRegister()));
+ } else {
+ ___ uxth(reg_w(reg.AsWRegister()), reg_w(reg.AsWRegister()));
+ }
}
void Arm64Assembler::VerifyObject(ManagedRegister /*src*/, bool /*could_be_null*/) {
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 823b818..cdf26f1 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1010,8 +1010,8 @@
}
if (compiler_filter_string == NULL) {
- if (instruction_set == kX86_64 || instruction_set == kArm64) {
- // TODO: currently x86-64 and arm64 are only interpreted.
+ if (instruction_set == kX86_64 || instruction_set == kArm64 || instruction_set == kMips) {
+ // TODO: implement/fix compilers for these architectures.
compiler_filter_string = "interpret-only";
} else if (image) {
compiler_filter_string = "speed";
diff --git a/disassembler/disassembler_arm.cc b/disassembler/disassembler_arm.cc
index d6d2058..4e4a512 100644
--- a/disassembler/disassembler_arm.cc
+++ b/disassembler/disassembler_arm.cc
@@ -1360,7 +1360,7 @@
} else if (Rn.r == 15) {
intptr_t lit_adr = reinterpret_cast<intptr_t>(instr_ptr);
lit_adr = RoundDown(lit_adr, 4) + 4 + imm12;
- args << " ; " << reinterpret_cast<void*>(*reinterpret_cast<int32_t*>(lit_adr));
+ args << StringPrintf(" ; 0x%08x", *reinterpret_cast<int32_t*>(lit_adr));
}
} else if (op3 == 3) {
// LDRSH.W Rt, [Rn, #imm12] - 111 11 00 11 011 nnnn tttt iiiiiiiiiiii
@@ -1373,7 +1373,7 @@
} else if (Rn.r == 15) {
intptr_t lit_adr = reinterpret_cast<intptr_t>(instr_ptr);
lit_adr = RoundDown(lit_adr, 4) + 4 + imm12;
- args << " ; " << reinterpret_cast<void*>(*reinterpret_cast<int32_t*>(lit_adr));
+ args << StringPrintf(" ; 0x%08x", *reinterpret_cast<int32_t*>(lit_adr));
}
}
}
@@ -1430,7 +1430,7 @@
} else if (Rn.r == 15) {
intptr_t lit_adr = reinterpret_cast<intptr_t>(instr_ptr);
lit_adr = RoundDown(lit_adr, 4) + 4 + imm12;
- args << " ; " << reinterpret_cast<void*>(*reinterpret_cast<int32_t*>(lit_adr));
+ args << StringPrintf(" ; 0x%08x", *reinterpret_cast<int32_t*>(lit_adr));
}
} else if (op4 == 0) {
// LDR.W Rt, [Rn, Rm{, LSL #imm2}] - 111 11 00 00 101 nnnn tttt 000000iimmmm
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 1a67952..412a052 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -495,7 +495,8 @@
} else {
uint32_t offset = StackVisitor::GetVRegOffset(code_item, oat_method.GetCoreSpillMask(),
oat_method.GetFpSpillMask(),
- oat_method.GetFrameSizeInBytes(), reg);
+ oat_method.GetFrameSizeInBytes(), reg,
+ GetInstructionSet());
os << "[sp + #" << offset << "]";
}
}
diff --git a/runtime/arch/stub_test.cc b/runtime/arch/stub_test.cc
index 7027b32..437beb5 100644
--- a/runtime/arch/stub_test.cc
+++ b/runtime/arch/stub_test.cc
@@ -633,4 +633,79 @@
#endif
}
+
+#if defined(__i386__) || defined(__arm__) || defined(__x86_64__)
+extern "C" void art_quick_string_compareto(void);
+#endif
+
+TEST_F(StubTest, StringCompareTo) {
+ TEST_DISABLED_FOR_HEAP_REFERENCE_POISONING();
+
+#if defined(__i386__) || defined(__arm__) || defined(__x86_64__)
+ // TODO: Check the "Unresolved" allocation stubs
+
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+ // garbage is created during ClassLinker::Init
+
+ // Create some strings
+ // Use array so we can index into it and use a matrix for expected results
+ constexpr size_t string_count = 7;
+ const char* c[string_count] = { "", "", "a", "aa", "ab", "aac", "aac" };
+
+ SirtRef<mirror::String>* s[string_count];
+
+ for (size_t i = 0; i < string_count; ++i) {
+ s[i] = new SirtRef<mirror::String>(soa.Self(), mirror::String::AllocFromModifiedUtf8(soa.Self(),
+ c[i]));
+ }
+
+ // TODO: wide characters
+
+ // Matrix of expectations. First component is first parameter. Note we only check against the
+ // sign, not the value.
+ int32_t expected[string_count][string_count] = {
+ { 0, 0, -1, -1, -1, -1, -1 }, // ""
+ { 0, 0, -1, -1, -1, -1, -1 }, // ""
+ { 1, 1, 0, -1, -1, -1, -1 }, // "a"
+ { 1, 1, 1, 0, -1, -1, -1 }, // "aa"
+ { 1, 1, 1, 1, 0, 1, 1 }, // "ab"
+ { 1, 1, 1, 1, -1, 0, 0 }, // "aac"
+ { 1, 1, 1, 1, -1, 0, 0 } // "aac"
+ // "" "" a aa ab aac aac
+ };
+
+ // Play with it...
+
+ for (size_t x = 0; x < string_count; ++x) {
+ for (size_t y = 0; y < string_count; ++y) {
+ // Test string_compareto x y
+ size_t result = Invoke3(reinterpret_cast<size_t>(s[x]->get()),
+ reinterpret_cast<size_t>(s[y]->get()), 0U,
+ reinterpret_cast<uintptr_t>(&art_quick_string_compareto), self);
+
+ EXPECT_FALSE(self->IsExceptionPending());
+
+ // The result is a 32b signed integer
+ union {
+ size_t r;
+ int32_t i;
+ } conv;
+ conv.r = result;
+ int32_t e = expected[x][y];
+ EXPECT_TRUE(e == 0 ? conv.i == 0 : true) << "x=" << c[x] << " y=" << c[y];
+ EXPECT_TRUE(e < 0 ? conv.i < 0 : true) << "x=" << c[x] << " y=" << c[y];
+ EXPECT_TRUE(e > 0 ? conv.i > 0 : true) << "x=" << c[x] << " y=" << c[y];
+ }
+ }
+
+ // Tests done.
+#else
+ LOG(INFO) << "Skipping string_compareto as I don't know how to do that on " << kRuntimeISA;
+ // Force-print to std::cout so it's also outside the logcat.
+ std::cout << "Skipping string_compareto as I don't know how to do that on " << kRuntimeISA <<
+ std::endl;
+#endif
+}
+
} // namespace art
diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
index 4fefd20..cac6cfd 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -1169,5 +1169,47 @@
UNIMPLEMENTED art_quick_deoptimize
UNIMPLEMENTED art_quick_indexof
-UNIMPLEMENTED art_quick_string_compareto
+
+ /*
+ * String's compareTo.
+ *
+ * On entry:
+ * rdi: this string object (known non-null)
+ * rsi: comp string object (known non-null)
+ */
+DEFINE_FUNCTION art_quick_string_compareto
+ movl STRING_COUNT_OFFSET(%edi), %r8d
+ movl STRING_COUNT_OFFSET(%esi), %r9d
+ movl STRING_VALUE_OFFSET(%edi), %r10d
+ movl STRING_VALUE_OFFSET(%esi), %r11d
+ movl STRING_OFFSET_OFFSET(%edi), %eax
+ movl STRING_OFFSET_OFFSET(%esi), %ecx
+ /* Build pointers to the start of string data */
+ leal STRING_DATA_OFFSET(%r10d, %eax, 2), %esi
+ leal STRING_DATA_OFFSET(%r11d, %ecx, 2), %edi
+ /* Calculate min length and count diff */
+ movl %r8d, %ecx
+ movl %r8d, %eax
+ subl %r9d, %eax
+ cmovg %r9d, %ecx
+ /*
+ * At this point we have:
+ * eax: value to return if first part of strings are equal
+ * ecx: minimum among the lengths of the two strings
+ * esi: pointer to this string data
+ * edi: pointer to comp string data
+ */
+ jecxz .Lkeep_length
+ repe cmpsw // find nonmatching chars in [%esi] and [%edi], up to length %ecx
+ jne .Lnot_equal
+.Lkeep_length:
+ ret
+ .balign 16
+.Lnot_equal:
+ movzwl -2(%esi), %eax // get last compared char from this string
+ movzwl -2(%edi), %ecx // get last compared char from comp string
+ subl %ecx, %eax // return the difference
+ ret
+END_FUNCTION art_quick_string_compareto
+
UNIMPLEMENTED art_quick_memcmp16
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index 2b29591..680ffbe 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -34,12 +34,6 @@
// Visits the arguments as saved to the stack by a Runtime::kRefAndArgs callee save frame.
class QuickArgumentVisitor {
- // Size of each spilled GPR.
-#ifdef __LP64__
- static constexpr size_t kBytesPerGprSpillLocation = 8;
-#else
- static constexpr size_t kBytesPerGprSpillLocation = 4;
-#endif
// Number of bytes for each out register in the caller method's frame.
static constexpr size_t kBytesStackArgLocation = 4;
#if defined(__arm__)
@@ -61,13 +55,12 @@
static constexpr bool kQuickSoftFloatAbi = true; // This is a soft float ABI.
static constexpr size_t kNumQuickGprArgs = 3; // 3 arguments passed in GPRs.
static constexpr size_t kNumQuickFprArgs = 0; // 0 arguments passed in FPRs.
- static constexpr size_t kBytesPerFprSpillLocation = 4; // FPR spill size is 4 bytes.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_Fpr1Offset = 0; // Offset of first FPR arg.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_Gpr1Offset = 8; // Offset of first GPR arg.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_LrOffset = 44; // Offset of return address.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_FrameSize = 48; // Frame size.
static size_t GprIndexToGprOffset(uint32_t gpr_index) {
- return gpr_index * kBytesPerGprSpillLocation;
+ return gpr_index * GetBytesPerGprSpillLocation(kRuntimeISA);
}
#elif defined(__aarch64__)
// The callee save frame is pointed to by SP.
@@ -93,13 +86,12 @@
static constexpr bool kQuickSoftFloatAbi = false; // This is a hard float ABI.
static constexpr size_t kNumQuickGprArgs = 7; // 7 arguments passed in GPRs.
static constexpr size_t kNumQuickFprArgs = 8; // 8 arguments passed in FPRs.
- static constexpr size_t kBytesPerFprSpillLocation = 8; // FPR spill size is 8 bytes.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_Fpr1Offset =16; // Offset of first FPR arg.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_Gpr1Offset = 144; // Offset of first GPR arg.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_LrOffset = 296; // Offset of return address.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_FrameSize = 304; // Frame size.
static size_t GprIndexToGprOffset(uint32_t gpr_index) {
- return gpr_index * kBytesPerGprSpillLocation;
+ return gpr_index * GetBytesPerGprSpillLocation(kRuntimeISA);
}
#elif defined(__mips__)
// The callee save frame is pointed to by SP.
@@ -119,13 +111,12 @@
static constexpr bool kQuickSoftFloatAbi = true; // This is a soft float ABI.
static constexpr size_t kNumQuickGprArgs = 3; // 3 arguments passed in GPRs.
static constexpr size_t kNumQuickFprArgs = 0; // 0 arguments passed in FPRs.
- static constexpr size_t kBytesPerFprSpillLocation = 4; // FPR spill size is 4 bytes.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_Fpr1Offset = 0; // Offset of first FPR arg.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_Gpr1Offset = 4; // Offset of first GPR arg.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_LrOffset = 60; // Offset of return address.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_FrameSize = 64; // Frame size.
static size_t GprIndexToGprOffset(uint32_t gpr_index) {
- return gpr_index * kBytesPerGprSpillLocation;
+ return gpr_index * GetBytesPerGprSpillLocation(kRuntimeISA);
}
#elif defined(__i386__)
// The callee save frame is pointed to by SP.
@@ -145,13 +136,12 @@
static constexpr bool kQuickSoftFloatAbi = true; // This is a soft float ABI.
static constexpr size_t kNumQuickGprArgs = 3; // 3 arguments passed in GPRs.
static constexpr size_t kNumQuickFprArgs = 0; // 0 arguments passed in FPRs.
- static constexpr size_t kBytesPerFprSpillLocation = 8; // FPR spill size is 8 bytes.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_Fpr1Offset = 0; // Offset of first FPR arg.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_Gpr1Offset = 4; // Offset of first GPR arg.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_LrOffset = 28; // Offset of return address.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_FrameSize = 32; // Frame size.
static size_t GprIndexToGprOffset(uint32_t gpr_index) {
- return gpr_index * kBytesPerGprSpillLocation;
+ return gpr_index * GetBytesPerGprSpillLocation(kRuntimeISA);
}
#elif defined(__x86_64__)
// The callee save frame is pointed to by SP.
@@ -184,18 +174,17 @@
static constexpr bool kQuickSoftFloatAbi = false; // This is a hard float ABI.
static constexpr size_t kNumQuickGprArgs = 5; // 3 arguments passed in GPRs.
static constexpr size_t kNumQuickFprArgs = 8; // 0 arguments passed in FPRs.
- static constexpr size_t kBytesPerFprSpillLocation = 8; // FPR spill size is 8 bytes.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_Fpr1Offset = 16; // Offset of first FPR arg.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_Gpr1Offset = 80; // Offset of first GPR arg.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_LrOffset = 168; // Offset of return address.
static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_FrameSize = 176; // Frame size.
static size_t GprIndexToGprOffset(uint32_t gpr_index) {
switch (gpr_index) {
- case 0: return (4 * kBytesPerGprSpillLocation);
- case 1: return (1 * kBytesPerGprSpillLocation);
- case 2: return (0 * kBytesPerGprSpillLocation);
- case 3: return (5 * kBytesPerGprSpillLocation);
- case 4: return (6 * kBytesPerGprSpillLocation);
+ case 0: return (4 * GetBytesPerGprSpillLocation(kRuntimeISA));
+ case 1: return (1 * GetBytesPerGprSpillLocation(kRuntimeISA));
+ case 2: return (0 * GetBytesPerGprSpillLocation(kRuntimeISA));
+ case 3: return (5 * GetBytesPerGprSpillLocation(kRuntimeISA));
+ case 4: return (6 * GetBytesPerGprSpillLocation(kRuntimeISA));
default:
LOG(FATAL) << "Unexpected GPR index: " << gpr_index;
return 0;
@@ -248,7 +237,7 @@
Primitive::Type type = GetParamPrimitiveType();
if (UNLIKELY((type == Primitive::kPrimDouble) || (type == Primitive::kPrimFloat))) {
if ((kNumQuickFprArgs != 0) && (fpr_index_ + 1 < kNumQuickFprArgs + 1)) {
- return fpr_args_ + (fpr_index_ * kBytesPerFprSpillLocation);
+ return fpr_args_ + (fpr_index_ * GetBytesPerFprSpillLocation(kRuntimeISA));
}
return stack_args_ + (stack_index_ * kBytesStackArgLocation);
}
@@ -260,7 +249,7 @@
}
bool IsSplitLongOrDouble() const {
- if ((kBytesPerGprSpillLocation == 4) || (kBytesPerFprSpillLocation == 4)) {
+ if ((GetBytesPerGprSpillLocation(kRuntimeISA) == 4) || (GetBytesPerFprSpillLocation(kRuntimeISA) == 4)) {
return is_split_long_or_double_;
} else {
return false; // An optimization for when GPR and FPRs are 64bit.
@@ -341,7 +330,7 @@
case Primitive::kPrimDouble:
case Primitive::kPrimLong:
if (kQuickSoftFloatAbi || (cur_type_ == Primitive::kPrimLong)) {
- is_split_long_or_double_ = (kBytesPerGprSpillLocation == 4) &&
+ is_split_long_or_double_ = (GetBytesPerGprSpillLocation(kRuntimeISA) == 4) &&
((gpr_index_ + 1) == kNumQuickGprArgs);
Visit();
if (!kQuickSoftFloatAbi || kNumQuickGprArgs == gpr_index_) {
@@ -354,7 +343,7 @@
}
if (gpr_index_ < kNumQuickGprArgs) {
gpr_index_++;
- if (kBytesPerGprSpillLocation == 4) {
+ if (GetBytesPerGprSpillLocation(kRuntimeISA) == 4) {
if (gpr_index_ < kNumQuickGprArgs) {
gpr_index_++;
} else if (kQuickSoftFloatAbi) {
@@ -363,12 +352,12 @@
}
}
} else {
- is_split_long_or_double_ = (kBytesPerFprSpillLocation == 4) &&
+ is_split_long_or_double_ = (GetBytesPerFprSpillLocation(kRuntimeISA) == 4) &&
((fpr_index_ + 1) == kNumQuickFprArgs);
Visit();
if ((kNumQuickFprArgs != 0) && (fpr_index_ + 1 < kNumQuickFprArgs + 1)) {
fpr_index_++;
- if (kBytesPerFprSpillLocation == 4) {
+ if (GetBytesPerFprSpillLocation(kRuntimeISA) == 4) {
if ((kNumQuickFprArgs != 0) && (fpr_index_ + 1 < kNumQuickFprArgs + 1)) {
fpr_index_++;
}
@@ -393,12 +382,13 @@
uint32_t shorty_len) {
if (kQuickSoftFloatAbi) {
CHECK_EQ(kNumQuickFprArgs, 0U);
- return (kNumQuickGprArgs * kBytesPerGprSpillLocation) + kBytesPerGprSpillLocation /* ArtMethod* */;
+ return (kNumQuickGprArgs * GetBytesPerGprSpillLocation(kRuntimeISA))
+ + GetBytesPerGprSpillLocation(kRuntimeISA) /* ArtMethod* */;
} else {
// For now, there is no reg-spill area for the targets with
// hard float ABI. So, the offset pointing to the first method's
// parameter ('this' for non-static methods) should be returned.
- return kBytesPerGprSpillLocation; // Skip Method*.
+ return GetBytesPerGprSpillLocation(kRuntimeISA); // Skip Method*.
}
}
diff --git a/runtime/gc/allocator/rosalloc-inl.h b/runtime/gc/allocator/rosalloc-inl.h
index ac0f67b..c69ca48 100644
--- a/runtime/gc/allocator/rosalloc-inl.h
+++ b/runtime/gc/allocator/rosalloc-inl.h
@@ -23,11 +23,17 @@
namespace gc {
namespace allocator {
+template<bool kThreadSafe>
inline ALWAYS_INLINE void* RosAlloc::Alloc(Thread* self, size_t size, size_t* bytes_allocated) {
if (UNLIKELY(size > kLargeSizeThreshold)) {
return AllocLargeObject(self, size, bytes_allocated);
}
- void* m = AllocFromRun(self, size, bytes_allocated);
+ void* m;
+ if (kThreadSafe) {
+ m = AllocFromRun(self, size, bytes_allocated);
+ } else {
+ m = AllocFromRunThreadUnsafe(self, size, bytes_allocated);
+ }
// Check if the returned memory is really all zero.
if (kCheckZeroMemory && m != nullptr) {
byte* bytes = reinterpret_cast<byte*>(m);
diff --git a/runtime/gc/allocator/rosalloc.cc b/runtime/gc/allocator/rosalloc.cc
index ff59016..f113030 100644
--- a/runtime/gc/allocator/rosalloc.cc
+++ b/runtime/gc/allocator/rosalloc.cc
@@ -67,11 +67,11 @@
<< std::hex << (intptr_t)(base_ + capacity_)
<< ", capacity=" << std::dec << capacity_
<< ", max_capacity=" << std::dec << max_capacity_;
- memset(current_runs_, 0, sizeof(current_runs_));
for (size_t i = 0; i < kNumOfSizeBrackets; i++) {
size_bracket_lock_names[i] =
StringPrintf("an rosalloc size bracket %d lock", static_cast<int>(i));
size_bracket_locks_[i] = new Mutex(size_bracket_lock_names[i].c_str(), kRosAllocBracketLock);
+ current_runs_[i] = dedicated_full_run_;
}
DCHECK_EQ(footprint_, capacity_);
size_t num_of_pages = footprint_ / kPageSize;
@@ -548,7 +548,7 @@
DCHECK(!new_run->IsThreadLocal());
DCHECK_EQ(new_run->first_search_vec_idx_, 0U);
DCHECK(!new_run->to_be_bulk_freed_);
- if (kUsePrefetchDuringAllocRun && idx <= kMaxThreadLocalSizeBracketIdx) {
+ if (kUsePrefetchDuringAllocRun && idx < kNumThreadLocalSizeBrackets) {
// Take ownership of the cache lines if we are likely to be thread local run.
if (kPrefetchNewRunDataByZeroing) {
// Zeroing the data is sometimes faster than prefetching but it increases memory usage
@@ -584,6 +584,60 @@
return AllocRun(self, idx);
}
+void* RosAlloc::AllocFromCurrentRunUnlocked(Thread* self, size_t idx) {
+ Run* current_run = current_runs_[idx];
+ DCHECK(current_run != nullptr);
+ void* slot_addr = current_run->AllocSlot();
+ if (UNLIKELY(slot_addr == nullptr)) {
+ // The current run got full. Try to refill it.
+ DCHECK(current_run->IsFull());
+ if (kIsDebugBuild && current_run != dedicated_full_run_) {
+ full_runs_[idx].insert(current_run);
+ if (kTraceRosAlloc) {
+ LOG(INFO) << __FUNCTION__ << " : Inserted run 0x" << std::hex << reinterpret_cast<intptr_t>(current_run)
+ << " into full_runs_[" << std::dec << idx << "]";
+ }
+ DCHECK(non_full_runs_[idx].find(current_run) == non_full_runs_[idx].end());
+ DCHECK(full_runs_[idx].find(current_run) != full_runs_[idx].end());
+ }
+ current_run = RefillRun(self, idx);
+ if (UNLIKELY(current_run == nullptr)) {
+ // Failed to allocate a new run, make sure that it is the dedicated full run.
+ current_runs_[idx] = dedicated_full_run_;
+ return nullptr;
+ }
+ DCHECK(current_run != nullptr);
+ DCHECK(non_full_runs_[idx].find(current_run) == non_full_runs_[idx].end());
+ DCHECK(full_runs_[idx].find(current_run) == full_runs_[idx].end());
+ current_run->SetIsThreadLocal(false);
+ current_runs_[idx] = current_run;
+ DCHECK(!current_run->IsFull());
+ slot_addr = current_run->AllocSlot();
+ // Must succeed now with a new run.
+ DCHECK(slot_addr != nullptr);
+ }
+ return slot_addr;
+}
+
+void* RosAlloc::AllocFromRunThreadUnsafe(Thread* self, size_t size, size_t* bytes_allocated) {
+ DCHECK_LE(size, kLargeSizeThreshold);
+ size_t bracket_size;
+ size_t idx = SizeToIndexAndBracketSize(size, &bracket_size);
+ DCHECK_EQ(idx, SizeToIndex(size));
+ DCHECK_EQ(bracket_size, IndexToBracketSize(idx));
+ DCHECK_EQ(bracket_size, bracketSizes[idx]);
+ DCHECK_LE(size, bracket_size);
+ DCHECK(size > 512 || bracket_size - size < 16);
+ Locks::mutator_lock_->AssertExclusiveHeld(self);
+ void* slot_addr = AllocFromCurrentRunUnlocked(self, idx);
+ if (LIKELY(slot_addr != nullptr)) {
+ DCHECK(bytes_allocated != nullptr);
+ *bytes_allocated = bracket_size;
+ // Caller verifies that it is all 0.
+ }
+ return slot_addr;
+}
+
void* RosAlloc::AllocFromRun(Thread* self, size_t size, size_t* bytes_allocated) {
DCHECK_LE(size, kLargeSizeThreshold);
size_t bracket_size;
@@ -596,7 +650,7 @@
void* slot_addr;
- if (LIKELY(idx <= kMaxThreadLocalSizeBracketIdx)) {
+ if (LIKELY(idx < kNumThreadLocalSizeBrackets)) {
// Use a thread-local run.
Run* thread_local_run = reinterpret_cast<Run*>(self->GetRosAllocRun(idx));
// Allow invalid since this will always fail the allocation.
@@ -631,7 +685,6 @@
// No slots got freed. Try to refill the thread-local run.
DCHECK(thread_local_run->IsFull());
if (thread_local_run != dedicated_full_run_) {
- self->SetRosAllocRun(idx, dedicated_full_run_);
thread_local_run->SetIsThreadLocal(false);
if (kIsDebugBuild) {
full_runs_[idx].insert(thread_local_run);
@@ -646,8 +699,9 @@
}
thread_local_run = RefillRun(self, idx);
- if (UNLIKELY(thread_local_run == NULL)) {
- return NULL;
+ if (UNLIKELY(thread_local_run == nullptr)) {
+ self->SetRosAllocRun(idx, dedicated_full_run_);
+ return nullptr;
}
DCHECK(non_full_runs_[idx].find(thread_local_run) == non_full_runs_[idx].end());
DCHECK(full_runs_[idx].find(thread_local_run) == full_runs_[idx].end());
@@ -656,12 +710,12 @@
DCHECK(!thread_local_run->IsFull());
}
- DCHECK(thread_local_run != NULL);
+ DCHECK(thread_local_run != nullptr);
DCHECK(!thread_local_run->IsFull());
DCHECK(thread_local_run->IsThreadLocal());
slot_addr = thread_local_run->AllocSlot();
// Must succeed now with a new run.
- DCHECK(slot_addr != NULL);
+ DCHECK(slot_addr != nullptr);
}
if (kTraceRosAlloc) {
LOG(INFO) << "RosAlloc::AllocFromRun() thread-local : 0x" << std::hex << reinterpret_cast<intptr_t>(slot_addr)
@@ -671,48 +725,7 @@
} else {
// Use the (shared) current run.
MutexLock mu(self, *size_bracket_locks_[idx]);
- Run* current_run = current_runs_[idx];
- if (UNLIKELY(current_run == NULL)) {
- current_run = RefillRun(self, idx);
- if (UNLIKELY(current_run == NULL)) {
- return NULL;
- }
- DCHECK(non_full_runs_[idx].find(current_run) == non_full_runs_[idx].end());
- DCHECK(full_runs_[idx].find(current_run) == full_runs_[idx].end());
- current_run->SetIsThreadLocal(false);
- current_runs_[idx] = current_run;
- DCHECK(!current_run->IsFull());
- }
- DCHECK(current_run != NULL);
- slot_addr = current_run->AllocSlot();
- if (UNLIKELY(slot_addr == NULL)) {
- // The current run got full. Try to refill it.
- DCHECK(current_run->IsFull());
- current_runs_[idx] = NULL;
- if (kIsDebugBuild) {
- // Insert it into full_runs and set the current run to NULL.
- full_runs_[idx].insert(current_run);
- if (kTraceRosAlloc) {
- LOG(INFO) << "RosAlloc::AllocFromRun() : Inserted run 0x" << std::hex << reinterpret_cast<intptr_t>(current_run)
- << " into full_runs_[" << std::dec << idx << "]";
- }
- }
- DCHECK(non_full_runs_[idx].find(current_run) == non_full_runs_[idx].end());
- DCHECK(full_runs_[idx].find(current_run) != full_runs_[idx].end());
- current_run = RefillRun(self, idx);
- if (UNLIKELY(current_run == NULL)) {
- return NULL;
- }
- DCHECK(current_run != NULL);
- DCHECK(non_full_runs_[idx].find(current_run) == non_full_runs_[idx].end());
- DCHECK(full_runs_[idx].find(current_run) == full_runs_[idx].end());
- current_run->SetIsThreadLocal(false);
- current_runs_[idx] = current_run;
- DCHECK(!current_run->IsFull());
- slot_addr = current_run->AllocSlot();
- // Must succeed now with a new run.
- DCHECK(slot_addr != NULL);
- }
+ slot_addr = AllocFromCurrentRunUnlocked(self, idx);
if (kTraceRosAlloc) {
LOG(INFO) << "RosAlloc::AllocFromRun() : 0x" << std::hex << reinterpret_cast<intptr_t>(slot_addr)
<< "-0x" << (reinterpret_cast<intptr_t>(slot_addr) + bracket_size)
@@ -741,7 +754,7 @@
}
if (LIKELY(run->IsThreadLocal())) {
// It's a thread-local run. Just mark the thread-local free bit map and return.
- DCHECK_LE(run->size_bracket_idx_, kMaxThreadLocalSizeBracketIdx);
+ DCHECK_LT(run->size_bracket_idx_, kNumThreadLocalSizeBrackets);
DCHECK(non_full_runs_[idx].find(run) == non_full_runs_[idx].end());
DCHECK(full_runs_[idx].find(run) == full_runs_[idx].end());
run->MarkThreadLocalFreeBitMap(ptr);
@@ -766,7 +779,7 @@
}
}
if (run == current_runs_[idx]) {
- current_runs_[idx] = NULL;
+ current_runs_[idx] = dedicated_full_run_;
}
DCHECK(non_full_runs_[idx].find(run) == non_full_runs_[idx].end());
DCHECK(full_runs_[idx].find(run) == full_runs_[idx].end());
@@ -1233,7 +1246,7 @@
size_t idx = run->size_bracket_idx_;
MutexLock mu(self, *size_bracket_locks_[idx]);
if (run->IsThreadLocal()) {
- DCHECK_LE(run->size_bracket_idx_, kMaxThreadLocalSizeBracketIdx);
+ DCHECK_LT(run->size_bracket_idx_, kNumThreadLocalSizeBrackets);
DCHECK(non_full_runs_[idx].find(run) == non_full_runs_[idx].end());
DCHECK(full_runs_[idx].find(run) == full_runs_[idx].end());
run->UnionBulkFreeBitMapToThreadLocalFreeBitMap();
@@ -1627,7 +1640,7 @@
Thread* self = Thread::Current();
// Avoid race conditions on the bulk free bit maps with BulkFree() (GC).
WriterMutexLock wmu(self, bulk_free_lock_);
- for (size_t idx = 0; idx < kNumOfSizeBrackets; idx++) {
+ for (size_t idx = 0; idx < kNumThreadLocalSizeBrackets; idx++) {
MutexLock mu(self, *size_bracket_locks_[idx]);
Run* thread_local_run = reinterpret_cast<Run*>(thread->GetRosAllocRun(idx));
CHECK(thread_local_run != nullptr);
@@ -1643,30 +1656,48 @@
thread_local_run->MergeBulkFreeBitMapIntoAllocBitMap();
DCHECK(non_full_runs_[idx].find(thread_local_run) == non_full_runs_[idx].end());
DCHECK(full_runs_[idx].find(thread_local_run) == full_runs_[idx].end());
- if (thread_local_run->IsFull()) {
- if (kIsDebugBuild) {
- full_runs_[idx].insert(thread_local_run);
- DCHECK(full_runs_[idx].find(thread_local_run) != full_runs_[idx].end());
- if (kTraceRosAlloc) {
- LOG(INFO) << "RosAlloc::RevokeThreadLocalRuns() : Inserted run 0x" << std::hex
- << reinterpret_cast<intptr_t>(thread_local_run)
- << " into full_runs_[" << std::dec << idx << "]";
- }
- }
- } else if (thread_local_run->IsAllFree()) {
- MutexLock mu(self, lock_);
- thread_local_run->ZeroHeader();
- FreePages(self, thread_local_run, true);
- } else {
- non_full_runs_[idx].insert(thread_local_run);
- DCHECK(non_full_runs_[idx].find(thread_local_run) != non_full_runs_[idx].end());
- if (kTraceRosAlloc) {
- LOG(INFO) << "RosAlloc::RevokeThreadLocalRuns() : Inserted run 0x" << std::hex
- << reinterpret_cast<intptr_t>(thread_local_run)
- << " into non_full_runs_[" << std::dec << idx << "]";
- }
+ RevokeRun(self, idx, thread_local_run);
+ }
+ }
+}
+
+void RosAlloc::RevokeRun(Thread* self, size_t idx, Run* run) {
+ size_bracket_locks_[idx]->AssertHeld(self);
+ DCHECK(run != dedicated_full_run_);
+ if (run->IsFull()) {
+ if (kIsDebugBuild) {
+ full_runs_[idx].insert(run);
+ DCHECK(full_runs_[idx].find(run) != full_runs_[idx].end());
+ if (kTraceRosAlloc) {
+ LOG(INFO) << __FUNCTION__ << " : Inserted run 0x" << std::hex
+ << reinterpret_cast<intptr_t>(run)
+ << " into full_runs_[" << std::dec << idx << "]";
}
}
+ } else if (run->IsAllFree()) {
+ run->ZeroHeader();
+ MutexLock mu(self, lock_);
+ FreePages(self, run, true);
+ } else {
+ non_full_runs_[idx].insert(run);
+ DCHECK(non_full_runs_[idx].find(run) != non_full_runs_[idx].end());
+ if (kTraceRosAlloc) {
+ LOG(INFO) << __FUNCTION__ << " : Inserted run 0x" << std::hex
+ << reinterpret_cast<intptr_t>(run)
+ << " into non_full_runs_[" << std::dec << idx << "]";
+ }
+ }
+}
+
+void RosAlloc::RevokeThreadUnsafeCurrentRuns() {
+ // Revoke the current runs which share the same idx as thread local runs.
+ Thread* self = Thread::Current();
+ for (size_t idx = 0; idx < kNumThreadLocalSizeBrackets; ++idx) {
+ MutexLock mu(self, *size_bracket_locks_[idx]);
+ if (current_runs_[idx] != dedicated_full_run_) {
+ RevokeRun(self, idx, current_runs_[idx]);
+ current_runs_[idx] = dedicated_full_run_;
+ }
}
}
@@ -1679,6 +1710,7 @@
for (Thread* thread : thread_list) {
RevokeThreadLocalRuns(thread);
}
+ RevokeThreadUnsafeCurrentRuns();
}
void RosAlloc::AssertThreadLocalRunsAreRevoked(Thread* thread) {
@@ -1686,7 +1718,7 @@
Thread* self = Thread::Current();
// Avoid race conditions on the bulk free bit maps with BulkFree() (GC).
WriterMutexLock wmu(self, bulk_free_lock_);
- for (size_t idx = 0; idx < kNumOfSizeBrackets; idx++) {
+ for (size_t idx = 0; idx < kNumThreadLocalSizeBrackets; idx++) {
MutexLock mu(self, *size_bracket_locks_[idx]);
Run* thread_local_run = reinterpret_cast<Run*>(thread->GetRosAllocRun(idx));
DCHECK(thread_local_run == nullptr || thread_local_run == dedicated_full_run_);
@@ -1696,18 +1728,21 @@
void RosAlloc::AssertAllThreadLocalRunsAreRevoked() {
if (kIsDebugBuild) {
- MutexLock mu(Thread::Current(), *Locks::runtime_shutdown_lock_);
- MutexLock mu2(Thread::Current(), *Locks::thread_list_lock_);
+ Thread* self = Thread::Current();
+ MutexLock mu(self, *Locks::runtime_shutdown_lock_);
+ MutexLock mu2(self, *Locks::thread_list_lock_);
std::list<Thread*> thread_list = Runtime::Current()->GetThreadList()->GetList();
for (Thread* t : thread_list) {
AssertThreadLocalRunsAreRevoked(t);
}
+ for (size_t idx = 0; idx < kNumThreadLocalSizeBrackets; ++idx) {
+ MutexLock mu(self, *size_bracket_locks_[idx]);
+ CHECK_EQ(current_runs_[idx], dedicated_full_run_);
+ }
}
}
void RosAlloc::Initialize() {
- // Check the consistency of the number of size brackets.
- DCHECK_EQ(Thread::kRosAllocNumOfSizeBrackets, kNumOfSizeBrackets);
// bracketSizes.
for (size_t i = 0; i < kNumOfSizeBrackets; i++) {
if (i < kNumOfSizeBrackets - 2) {
@@ -1911,15 +1946,34 @@
break;
}
case kPageMapRunPart:
- LOG(FATAL) << "Unreachable - page map type: " << pm << std::endl << DumpPageMap();
- break;
+ // Fall-through.
default:
LOG(FATAL) << "Unreachable - page map type: " << pm << std::endl << DumpPageMap();
break;
}
}
}
-
+ std::list<Thread*> threads = Runtime::Current()->GetThreadList()->GetList();
+ for (Thread* thread : threads) {
+ for (size_t i = 0; i < kNumThreadLocalSizeBrackets; ++i) {
+ MutexLock mu(self, *size_bracket_locks_[i]);
+ Run* thread_local_run = reinterpret_cast<Run*>(thread->GetRosAllocRun(i));
+ CHECK(thread_local_run != nullptr);
+ CHECK(thread_local_run->IsThreadLocal());
+ CHECK(thread_local_run == dedicated_full_run_ ||
+ thread_local_run->size_bracket_idx_ == i);
+ }
+ }
+ for (size_t i = 0; i < kNumOfSizeBrackets; i++) {
+ MutexLock mu(self, *size_bracket_locks_[i]);
+ Run* current_run = current_runs_[i];
+ CHECK(current_run != nullptr);
+ if (current_run != dedicated_full_run_) {
+ // The dedicated full run is currently marked as thread local.
+ CHECK(!current_run->IsThreadLocal());
+ CHECK_EQ(current_run->size_bracket_idx_, i);
+ }
+ }
// Call Verify() here for the lock order.
for (auto& run : runs) {
run->Verify(self, this);
@@ -1952,7 +2006,7 @@
std::list<Thread*> thread_list = Runtime::Current()->GetThreadList()->GetList();
for (auto it = thread_list.begin(); it != thread_list.end(); ++it) {
Thread* thread = *it;
- for (size_t i = 0; i < kNumOfSizeBrackets; i++) {
+ for (size_t i = 0; i < kNumThreadLocalSizeBrackets; i++) {
MutexLock mu(self, *rosalloc->size_bracket_locks_[i]);
Run* thread_local_run = reinterpret_cast<Run*>(thread->GetRosAllocRun(i));
if (thread_local_run == this) {
diff --git a/runtime/gc/allocator/rosalloc.h b/runtime/gc/allocator/rosalloc.h
index f7fa2da..21044f3 100644
--- a/runtime/gc/allocator/rosalloc.h
+++ b/runtime/gc/allocator/rosalloc.h
@@ -405,11 +405,6 @@
// at a page-granularity.
static const size_t kLargeSizeThreshold = 2048;
- // We use use thread-local runs for the size Brackets whose indexes
- // are less than or equal to this index. We use shared (current)
- // runs for the rest.
- static const size_t kMaxThreadLocalSizeBracketIdx = 10;
-
// If true, check that the returned memory is actually zero.
static constexpr bool kCheckZeroMemory = kIsDebugBuild;
@@ -442,6 +437,10 @@
// The default value for page_release_size_threshold_.
static constexpr size_t kDefaultPageReleaseSizeThreshold = 4 * MB;
+ // We use thread-local runs for the size Brackets whose indexes
+ // are less than this index. We use shared (current) runs for the rest.
+ static const size_t kNumThreadLocalSizeBrackets = 11;
+
private:
// The base address of the memory region that's managed by this allocator.
byte* base_;
@@ -526,6 +525,12 @@
// Allocate/free a run slot.
void* AllocFromRun(Thread* self, size_t size, size_t* bytes_allocated)
LOCKS_EXCLUDED(lock_);
+ // Allocate/free a run slot without acquiring locks.
+ // TODO: EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_)
+ void* AllocFromRunThreadUnsafe(Thread* self, size_t size, size_t* bytes_allocated)
+ LOCKS_EXCLUDED(lock_);
+ void* AllocFromCurrentRunUnlocked(Thread* self, size_t idx);
+
// Returns the bracket size.
size_t FreeFromRun(Thread* self, void* ptr, Run* run)
LOCKS_EXCLUDED(lock_);
@@ -543,11 +548,20 @@
// Allocates large objects.
void* AllocLargeObject(Thread* self, size_t size, size_t* bytes_allocated) LOCKS_EXCLUDED(lock_);
+ // Revoke a run by adding it to non_full_runs_ or freeing the pages.
+ void RevokeRun(Thread* self, size_t idx, Run* run);
+
+ // Revoke the current runs which share an index with the thread local runs.
+ void RevokeThreadUnsafeCurrentRuns();
+
public:
RosAlloc(void* base, size_t capacity, size_t max_capacity,
PageReleaseMode page_release_mode,
size_t page_release_size_threshold = kDefaultPageReleaseSizeThreshold);
~RosAlloc();
+ // If kThreadUnsafe is true then the allocator may avoid acquiring some locks as an optimization.
+ // If used, this may cause race conditions if multiple threads are allocating at the same time.
+ template<bool kThreadSafe = true>
void* Alloc(Thread* self, size_t size, size_t* bytes_allocated)
LOCKS_EXCLUDED(lock_);
size_t Free(Thread* self, void* ptr)
diff --git a/runtime/gc/collector/semi_space.cc b/runtime/gc/collector/semi_space.cc
index 0b26019..b53ee10 100644
--- a/runtime/gc/collector/semi_space.cc
+++ b/runtime/gc/collector/semi_space.cc
@@ -180,7 +180,9 @@
runtime->SetFaultMessage(oss.str());
CHECK_EQ(self_->SetStateUnsafe(old_state), kRunnable);
}
-
+ // Revoke the thread local buffers since the GC may allocate into a RosAllocSpace and this helps
+ // to prevent fragmentation.
+ RevokeAllThreadLocalBuffers();
if (generational_) {
if (gc_cause_ == kGcCauseExplicit || gc_cause_ == kGcCauseForNativeAlloc ||
clear_soft_references_) {
@@ -332,11 +334,8 @@
class SemiSpaceScanObjectVisitor {
public:
explicit SemiSpaceScanObjectVisitor(SemiSpace* ss) : semi_space_(ss) {}
- void operator()(Object* obj) const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_)
- EXCLUSIVE_LOCKS_REQUIRED(Locks::heap_bitmap_lock_) {
- // TODO: fix NO_THREAD_SAFETY_ANALYSIS. ScanObject() requires an
- // exclusive lock on the mutator lock, but
- // SpaceBitmap::VisitMarkedRange() only requires the shared lock.
+ void operator()(Object* obj) const EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_,
+ Locks::heap_bitmap_lock_) {
DCHECK(obj != nullptr);
semi_space_->ScanObject(obj);
}
@@ -552,10 +551,11 @@
// (pseudo-promote) it to the main free list space (as sort
// of an old generation.)
space::MallocSpace* promo_dest_space = GetHeap()->GetPrimaryFreeListSpace();
- forward_address = promo_dest_space->Alloc(self_, object_size, &bytes_allocated, nullptr);
+ forward_address = promo_dest_space->AllocThreadUnsafe(self_, object_size, &bytes_allocated,
+ nullptr);
if (UNLIKELY(forward_address == nullptr)) {
// If out of space, fall back to the to-space.
- forward_address = to_space_->Alloc(self_, object_size, &bytes_allocated, nullptr);
+ forward_address = to_space_->AllocThreadUnsafe(self_, object_size, &bytes_allocated, nullptr);
} else {
bytes_promoted_ += bytes_allocated;
// Dirty the card at the destionation as it may contain
@@ -599,7 +599,7 @@
DCHECK(forward_address != nullptr);
} else {
// If it's allocated after the last GC (younger), copy it to the to-space.
- forward_address = to_space_->Alloc(self_, object_size, &bytes_allocated, nullptr);
+ forward_address = to_space_->AllocThreadUnsafe(self_, object_size, &bytes_allocated, nullptr);
}
++objects_moved_;
bytes_moved_ += bytes_allocated;
diff --git a/runtime/gc/collector/semi_space.h b/runtime/gc/collector/semi_space.h
index 9b6df16..3b3e1b1 100644
--- a/runtime/gc/collector/semi_space.h
+++ b/runtime/gc/collector/semi_space.h
@@ -98,12 +98,10 @@
// Returns the new address of the object.
template<bool kPoisonReferences>
void MarkObject(mirror::ObjectReference<kPoisonReferences, mirror::Object>* obj_ptr)
- EXCLUSIVE_LOCKS_REQUIRED(Locks::heap_bitmap_lock_)
- SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ EXCLUSIVE_LOCKS_REQUIRED(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
void ScanObject(mirror::Object* obj)
- EXCLUSIVE_LOCKS_REQUIRED(Locks::heap_bitmap_lock_)
- SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ EXCLUSIVE_LOCKS_REQUIRED(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
void VerifyNoFromSpaceReferences(mirror::Object* obj)
SHARED_LOCKS_REQUIRED(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
@@ -150,8 +148,7 @@
SHARED_LOCKS_REQUIRED(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
virtual mirror::Object* MarkNonForwardedObject(mirror::Object* obj)
- EXCLUSIVE_LOCKS_REQUIRED(Locks::heap_bitmap_lock_)
- SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ EXCLUSIVE_LOCKS_REQUIRED(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
// Schedules an unmarked object for reference processing.
void DelayReferenceReferent(mirror::Class* klass, mirror::Reference* reference)
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 50ef7a6..5d517bb 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -358,16 +358,16 @@
can_move_objects = !have_zygote_space_;
}
if (kUseRosAlloc) {
- main_space_ = space::RosAllocSpace::CreateFromMemMap(mem_map, "main rosalloc space",
- kDefaultStartingSize, initial_size,
- growth_limit, capacity, low_memory_mode_,
- can_move_objects);
+ rosalloc_space_ = space::RosAllocSpace::CreateFromMemMap(
+ mem_map, "main rosalloc space", kDefaultStartingSize, initial_size, growth_limit, capacity,
+ low_memory_mode_, can_move_objects);
+ main_space_ = rosalloc_space_;
CHECK(main_space_ != nullptr) << "Failed to create rosalloc space";
} else {
- main_space_ = space::DlMallocSpace::CreateFromMemMap(mem_map, "main dlmalloc space",
- kDefaultStartingSize, initial_size,
- growth_limit, capacity,
- can_move_objects);
+ dlmalloc_space_ = space::DlMallocSpace::CreateFromMemMap(
+ mem_map, "main dlmalloc space", kDefaultStartingSize, initial_size, growth_limit, capacity,
+ can_move_objects);
+ main_space_ = rosalloc_space_;
CHECK(main_space_ != nullptr) << "Failed to create dlmalloc space";
}
main_space_->SetFootprintLimit(main_space_->Capacity());
@@ -580,7 +580,7 @@
thread_pool_.reset(nullptr);
}
-void Heap::AddSpace(space::Space* space, bool set_as_default) {
+void Heap::AddSpace(space::Space* space) {
DCHECK(space != nullptr);
WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
if (space->IsContinuousSpace()) {
@@ -595,18 +595,6 @@
mark_bitmap_->AddContinuousSpaceBitmap(mark_bitmap);
}
continuous_spaces_.push_back(continuous_space);
- if (set_as_default) {
- if (continuous_space->IsDlMallocSpace()) {
- dlmalloc_space_ = continuous_space->AsDlMallocSpace();
- } else if (continuous_space->IsRosAllocSpace()) {
- // Revoke before if we already have a rosalloc_space_ so that we don't end up with non full
- // runs from the previous one during the revoke after.
- if (rosalloc_space_ != nullptr) {
- rosalloc_space_->RevokeAllThreadLocalBuffers();
- }
- rosalloc_space_ = continuous_space->AsRosAllocSpace();
- }
- }
// Ensure that spaces remain sorted in increasing order of start address.
std::sort(continuous_spaces_.begin(), continuous_spaces_.end(),
[](const space::ContinuousSpace* a, const space::ContinuousSpace* b) {
@@ -624,7 +612,16 @@
}
}
-void Heap::RemoveSpace(space::Space* space, bool unset_as_default) {
+void Heap::SetSpaceAsDefault(space::ContinuousSpace* continuous_space) {
+ WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
+ if (continuous_space->IsDlMallocSpace()) {
+ dlmalloc_space_ = continuous_space->AsDlMallocSpace();
+ } else if (continuous_space->IsRosAllocSpace()) {
+ rosalloc_space_ = continuous_space->AsRosAllocSpace();
+ }
+}
+
+void Heap::RemoveSpace(space::Space* space) {
DCHECK(space != nullptr);
WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
if (space->IsContinuousSpace()) {
@@ -641,20 +638,6 @@
auto it = std::find(continuous_spaces_.begin(), continuous_spaces_.end(), continuous_space);
DCHECK(it != continuous_spaces_.end());
continuous_spaces_.erase(it);
- if (unset_as_default) {
- if (continuous_space == dlmalloc_space_) {
- dlmalloc_space_ = nullptr;
- } else if (continuous_space == rosalloc_space_) {
- rosalloc_space_ = nullptr;
- }
- if (continuous_space == main_space_) {
- main_space_ = nullptr;
- } else if (continuous_space == bump_pointer_space_) {
- bump_pointer_space_ = nullptr;
- } else if (continuous_space == temp_space_) {
- temp_space_ = nullptr;
- }
- }
} else {
DCHECK(space->IsDiscontinuousSpace());
space::DiscontinuousSpace* discontinuous_space = space->AsDiscontinuousSpace();
@@ -1470,7 +1453,7 @@
// Remove the main space so that we don't try to trim it, this doens't work for debug
// builds since RosAlloc attempts to read the magic number from a protected page.
// TODO: Clean this up by getting rid of the remove_as_default parameter.
- RemoveSpace(main_space_, false);
+ RemoveSpace(main_space_);
}
break;
}
@@ -1479,7 +1462,7 @@
case kCollectorTypeCMS: {
if (IsMovingGc(collector_type_)) {
// Compact to the main space from the bump pointer space, don't need to swap semispaces.
- AddSpace(main_space_, false);
+ AddSpace(main_space_);
main_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
Compact(main_space_, bump_pointer_space_);
}
@@ -1694,14 +1677,8 @@
reset_main_space = true;
}
zygote_collector.SetToSpace(&target_space);
-
- Runtime::Current()->GetThreadList()->SuspendAll();
+ zygote_collector.SetSwapSemiSpaces(false);
zygote_collector.Run(kGcCauseCollectorTransition, false);
- if (IsMovingGc(collector_type_)) {
- SwapSemiSpaces();
- }
- Runtime::Current()->GetThreadList()->ResumeAll();
-
if (reset_main_space) {
main_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
madvise(main_space_->Begin(), main_space_->Capacity(), MADV_DONTNEED);
@@ -1747,7 +1724,7 @@
&non_moving_space_);
delete old_alloc_space;
CHECK(zygote_space != nullptr) << "Failed creating zygote space";
- AddSpace(zygote_space, false);
+ AddSpace(zygote_space);
non_moving_space_->SetFootprintLimit(non_moving_space_->Capacity());
AddSpace(non_moving_space_);
have_zygote_space_ = true;
@@ -2397,8 +2374,7 @@
}
void Heap::PreGcVerification(collector::GarbageCollector* gc) {
- if (verify_pre_gc_heap_ || verify_missing_card_marks_ || verify_mod_union_table_ ||
- verify_pre_gc_rosalloc_) {
+ if (verify_pre_gc_heap_ || verify_missing_card_marks_ || verify_mod_union_table_) {
collector::GarbageCollector::ScopedPause pause(gc);
PreGcVerificationPaused(gc);
}
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 5533f3d..d3b5cdc 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -283,11 +283,12 @@
void RegisterGCAllocation(size_t bytes);
void RegisterGCDeAllocation(size_t bytes);
- // Public due to usage by tests.
- void AddSpace(space::Space* space, bool set_as_default = true)
+ // Set the heap's private space pointers to be the same as the space based on it's type. Public
+ // due to usage by tests.
+ void SetSpaceAsDefault(space::ContinuousSpace* continuous_space)
LOCKS_EXCLUDED(Locks::heap_bitmap_lock_);
- void RemoveSpace(space::Space* space, bool unset_as_default = true)
- LOCKS_EXCLUDED(Locks::heap_bitmap_lock_);
+ void AddSpace(space::Space* space) LOCKS_EXCLUDED(Locks::heap_bitmap_lock_);
+ void RemoveSpace(space::Space* space) LOCKS_EXCLUDED(Locks::heap_bitmap_lock_);
// Set target ideal heap utilization ratio, implements
// dalvik.system.VMRuntime.setTargetHeapUtilization.
diff --git a/runtime/gc/space/bump_pointer_space-inl.h b/runtime/gc/space/bump_pointer_space-inl.h
index 70ab64b..497a61f 100644
--- a/runtime/gc/space/bump_pointer_space-inl.h
+++ b/runtime/gc/space/bump_pointer_space-inl.h
@@ -36,6 +36,26 @@
return ret;
}
+inline mirror::Object* BumpPointerSpace::AllocThreadUnsafe(Thread* self, size_t num_bytes,
+ size_t* bytes_allocated,
+ size_t* usable_size) {
+ Locks::mutator_lock_->AssertExclusiveHeld(self);
+ num_bytes = RoundUp(num_bytes, kAlignment);
+ if (end_ + num_bytes > growth_end_) {
+ return nullptr;
+ }
+ mirror::Object* obj = reinterpret_cast<mirror::Object*>(end_);
+ end_ += num_bytes;
+ *bytes_allocated = num_bytes;
+ // Use the CAS free versions as an optimization.
+ objects_allocated_ = objects_allocated_ + 1;
+ bytes_allocated_ = bytes_allocated_ + num_bytes;
+ if (UNLIKELY(usable_size != nullptr)) {
+ *usable_size = num_bytes;
+ }
+ return obj;
+}
+
inline mirror::Object* BumpPointerSpace::AllocNonvirtualWithoutAccounting(size_t num_bytes) {
DCHECK(IsAligned<kAlignment>(num_bytes));
byte* old_end;
diff --git a/runtime/gc/space/bump_pointer_space.h b/runtime/gc/space/bump_pointer_space.h
index e52a9a3..9e61f30 100644
--- a/runtime/gc/space/bump_pointer_space.h
+++ b/runtime/gc/space/bump_pointer_space.h
@@ -48,6 +48,11 @@
// Allocate num_bytes, returns nullptr if the space is full.
mirror::Object* Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated,
size_t* usable_size) OVERRIDE;
+ // Thread-unsafe allocation for when mutators are suspended, used by the semispace collector.
+ mirror::Object* AllocThreadUnsafe(Thread* self, size_t num_bytes, size_t* bytes_allocated,
+ size_t* usable_size)
+ OVERRIDE EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_);
+
mirror::Object* AllocNonvirtual(size_t num_bytes);
mirror::Object* AllocNonvirtualWithoutAccounting(size_t num_bytes);
diff --git a/runtime/gc/space/rosalloc_space-inl.h b/runtime/gc/space/rosalloc_space-inl.h
index d270885..fbfef45 100644
--- a/runtime/gc/space/rosalloc_space-inl.h
+++ b/runtime/gc/space/rosalloc_space-inl.h
@@ -46,11 +46,15 @@
return size_by_size;
}
+template<bool kThreadSafe>
inline mirror::Object* RosAllocSpace::AllocCommon(Thread* self, size_t num_bytes,
size_t* bytes_allocated, size_t* usable_size) {
size_t rosalloc_size = 0;
+ if (!kThreadSafe) {
+ Locks::mutator_lock_->AssertExclusiveHeld(self);
+ }
mirror::Object* result = reinterpret_cast<mirror::Object*>(
- rosalloc_->Alloc(self, num_bytes, &rosalloc_size));
+ rosalloc_->Alloc<kThreadSafe>(self, num_bytes, &rosalloc_size));
if (LIKELY(result != NULL)) {
if (kDebugSpaces) {
CHECK(Contains(result)) << "Allocation (" << reinterpret_cast<void*>(result)
diff --git a/runtime/gc/space/rosalloc_space.cc b/runtime/gc/space/rosalloc_space.cc
index f5c0e94..a1511e7 100644
--- a/runtime/gc/space/rosalloc_space.cc
+++ b/runtime/gc/space/rosalloc_space.cc
@@ -159,7 +159,7 @@
}
// Note RosAlloc zeroes memory internally.
// Return the new allocation or NULL.
- CHECK(!kDebugSpaces || result == NULL || Contains(result));
+ CHECK(!kDebugSpaces || result == nullptr || Contains(result));
return result;
}
diff --git a/runtime/gc/space/rosalloc_space.h b/runtime/gc/space/rosalloc_space.h
index a156738..2934af8 100644
--- a/runtime/gc/space/rosalloc_space.h
+++ b/runtime/gc/space/rosalloc_space.h
@@ -52,6 +52,11 @@
size_t* usable_size) OVERRIDE {
return AllocNonvirtual(self, num_bytes, bytes_allocated, usable_size);
}
+ mirror::Object* AllocThreadUnsafe(Thread* self, size_t num_bytes, size_t* bytes_allocated,
+ size_t* usable_size)
+ OVERRIDE EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return AllocNonvirtualThreadUnsafe(self, num_bytes, bytes_allocated, usable_size);
+ }
size_t AllocationSize(mirror::Object* obj, size_t* usable_size) OVERRIDE {
return AllocationSizeNonvirtual(obj, usable_size);
}
@@ -65,6 +70,11 @@
// RosAlloc zeroes memory internally.
return AllocCommon(self, num_bytes, bytes_allocated, usable_size);
}
+ mirror::Object* AllocNonvirtualThreadUnsafe(Thread* self, size_t num_bytes,
+ size_t* bytes_allocated, size_t* usable_size) {
+ // RosAlloc zeroes memory internally. Pass in false for thread unsafe.
+ return AllocCommon<false>(self, num_bytes, bytes_allocated, usable_size);
+ }
// TODO: NO_THREAD_SAFETY_ANALYSIS because SizeOf() requires that mutator_lock is held.
size_t AllocationSizeNonvirtual(mirror::Object* obj, size_t* usable_size)
@@ -116,6 +126,7 @@
size_t starting_size, size_t initial_size, bool low_memory_mode);
private:
+ template<bool kThreadSafe = true>
mirror::Object* AllocCommon(Thread* self, size_t num_bytes, size_t* bytes_allocated,
size_t* usable_size);
diff --git a/runtime/gc/space/space.h b/runtime/gc/space/space.h
index 0a87a16..dcf5357 100644
--- a/runtime/gc/space/space.h
+++ b/runtime/gc/space/space.h
@@ -203,9 +203,17 @@
// Allocate num_bytes without allowing growth. If the allocation
// succeeds, the output parameter bytes_allocated will be set to the
// actually allocated bytes which is >= num_bytes.
+ // Alloc can be called from multiple threads at the same time and must be thread-safe.
virtual mirror::Object* Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated,
size_t* usable_size) = 0;
+ // Thread-unsafe allocation for when mutators are suspended, used by the semispace collector.
+ virtual mirror::Object* AllocThreadUnsafe(Thread* self, size_t num_bytes, size_t* bytes_allocated,
+ size_t* usable_size)
+ EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return Alloc(self, num_bytes, bytes_allocated, usable_size);
+ }
+
// Return the storage space required by obj.
virtual size_t AllocationSize(mirror::Object* obj, size_t* usable_size) = 0;
diff --git a/runtime/gc/space/space_test.h b/runtime/gc/space/space_test.h
index 28200df..3335e72 100644
--- a/runtime/gc/space/space_test.h
+++ b/runtime/gc/space/space_test.h
@@ -38,9 +38,13 @@
SpaceTest() : byte_array_class_(nullptr) {
}
- void AddSpace(ContinuousSpace* space) {
- // By passing true, AddSpace() does the revoke.
- Runtime::Current()->GetHeap()->AddSpace(space, true);
+ void AddSpace(ContinuousSpace* space, bool revoke = true) {
+ Heap* heap = Runtime::Current()->GetHeap();
+ if (revoke) {
+ heap->RevokeAllThreadLocalBuffers();
+ }
+ heap->AddSpace(space);
+ heap->SetSpaceAsDefault(space);
}
mirror::Class* GetByteArrayClass(Thread* self) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
@@ -227,15 +231,16 @@
gc::Heap* heap = Runtime::Current()->GetHeap();
space::Space* old_space = space;
heap->RemoveSpace(old_space);
+ heap->RevokeAllThreadLocalBuffers();
space::ZygoteSpace* zygote_space = space->CreateZygoteSpace("alloc space",
heap->IsLowMemoryMode(),
&space);
delete old_space;
// Add the zygote space.
- AddSpace(zygote_space);
+ AddSpace(zygote_space, false);
// Make space findable to the heap, will also delete space when runtime is cleaned up
- AddSpace(space);
+ AddSpace(space, false);
// Succeeds, fits without adjusting the footprint limit.
ptr1.reset(Alloc(space, self, 1 * MB, &ptr1_bytes_allocated, &ptr1_usable_size));
diff --git a/runtime/instruction_set.cc b/runtime/instruction_set.cc
index cbcd2e0..c1931a9 100644
--- a/runtime/instruction_set.cc
+++ b/runtime/instruction_set.cc
@@ -86,6 +86,52 @@
}
}
+size_t GetBytesPerGprSpillLocation(InstructionSet isa) {
+ switch (isa) {
+ case kArm:
+ // Fall-through.
+ case kThumb2:
+ return 4;
+ case kArm64:
+ return 8;
+ case kX86:
+ return 4;
+ case kX86_64:
+ return 8;
+ case kMips:
+ return 4;
+ case kNone:
+ LOG(FATAL) << "ISA kNone does not have spills.";
+ return 0;
+ default:
+ LOG(FATAL) << "Unknown ISA " << isa;
+ return 0;
+ }
+}
+
+size_t GetBytesPerFprSpillLocation(InstructionSet isa) {
+ switch (isa) {
+ case kArm:
+ // Fall-through.
+ case kThumb2:
+ return 4;
+ case kArm64:
+ return 8;
+ case kX86:
+ return 8;
+ case kX86_64:
+ return 8;
+ case kMips:
+ return 4;
+ case kNone:
+ LOG(FATAL) << "ISA kNone does not have spills.";
+ return 0;
+ default:
+ LOG(FATAL) << "Unknown ISA " << isa;
+ return 0;
+ }
+}
+
size_t GetInstructionSetAlignment(InstructionSet isa) {
switch (isa) {
case kArm:
diff --git a/runtime/instruction_set.h b/runtime/instruction_set.h
index 4bc35a7..bfbbbd6 100644
--- a/runtime/instruction_set.h
+++ b/runtime/instruction_set.h
@@ -41,6 +41,8 @@
size_t GetInstructionSetPointerSize(InstructionSet isa);
size_t GetInstructionSetAlignment(InstructionSet isa);
bool Is64BitInstructionSet(InstructionSet isa);
+size_t GetBytesPerGprSpillLocation(InstructionSet isa);
+size_t GetBytesPerFprSpillLocation(InstructionSet isa);
#if defined(__arm__)
static constexpr InstructionSet kRuntimeISA = kArm;
diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc
index a91fdf1..aee0d64 100644
--- a/runtime/quick_exception_handler.cc
+++ b/runtime/quick_exception_handler.cc
@@ -19,6 +19,7 @@
#include "catch_block_stack_visitor.h"
#include "deoptimize_stack_visitor.h"
#include "entrypoints/entrypoint_utils.h"
+#include "mirror/art_method-inl.h"
#include "sirt_ref-inl.h"
namespace art {
diff --git a/runtime/stack.cc b/runtime/stack.cc
index ab3bd85..9c709ae 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -187,7 +187,7 @@
uint32_t core_spills = m->GetCoreSpillMask();
uint32_t fp_spills = m->GetFpSpillMask();
size_t frame_size = m->GetFrameSizeInBytes();
- int offset = GetVRegOffset(code_item, core_spills, fp_spills, frame_size, vreg);
+ int offset = GetVRegOffset(code_item, core_spills, fp_spills, frame_size, vreg, kRuntimeISA);
byte* vreg_addr = reinterpret_cast<byte*>(GetCurrentQuickFrame()) + offset;
*reinterpret_cast<uint32_t*>(vreg_addr) = new_value;
}
diff --git a/runtime/stack.h b/runtime/stack.h
index ab903d6..afc4f25 100644
--- a/runtime/stack.h
+++ b/runtime/stack.h
@@ -19,9 +19,10 @@
#include "dex_file.h"
#include "instrumentation.h"
+#include "arch/context.h"
#include "base/casts.h"
#include "base/macros.h"
-#include "arch/context.h"
+#include "instruction_set.h"
#include "mirror/object.h"
#include "mirror/object_reference.h"
#include "verify_object.h"
@@ -577,7 +578,7 @@
uint32_t* GetVRegAddr(mirror::ArtMethod** cur_quick_frame, const DexFile::CodeItem* code_item,
uint32_t core_spills, uint32_t fp_spills, size_t frame_size,
uint16_t vreg) const {
- int offset = GetVRegOffset(code_item, core_spills, fp_spills, frame_size, vreg);
+ int offset = GetVRegOffset(code_item, core_spills, fp_spills, frame_size, vreg, kRuntimeISA);
DCHECK_EQ(cur_quick_frame, GetCurrentQuickFrame());
byte* vreg_addr = reinterpret_cast<byte*>(cur_quick_frame) + offset;
return reinterpret_cast<uint32_t*>(vreg_addr);
@@ -634,14 +635,15 @@
*/
static int GetVRegOffset(const DexFile::CodeItem* code_item,
uint32_t core_spills, uint32_t fp_spills,
- size_t frame_size, int reg) {
+ size_t frame_size, int reg, InstructionSet isa) {
DCHECK_EQ(frame_size & (kStackAlignment - 1), 0U);
DCHECK_NE(reg, static_cast<int>(kVRegInvalid));
-
- int num_spills = __builtin_popcount(core_spills) + __builtin_popcount(fp_spills) + 1; // Filler.
+ int spill_size = __builtin_popcount(core_spills) * GetBytesPerGprSpillLocation(isa)
+ + __builtin_popcount(fp_spills) * GetBytesPerFprSpillLocation(isa)
+ + sizeof(uint32_t); // Filler.
int num_ins = code_item->ins_size_;
int num_regs = code_item->registers_size_ - num_ins;
- int locals_start = frame_size - ((num_spills + num_regs) * sizeof(uint32_t));
+ int locals_start = frame_size - spill_size - num_regs * sizeof(uint32_t);
if (reg == static_cast<int>(kVRegMethodPtrBaseReg)) {
// The current method pointer corresponds to special location on stack.
return 0;
@@ -654,19 +656,20 @@
* temp is at offset -4 bytes from locals, the second is at -8 bytes from locals,
* and so on.
*/
- int relative_offset = (reg + std::abs(static_cast<int>(kVRegNonSpecialTempBaseReg)) - 1) * sizeof(uint32_t);
+ int relative_offset =
+ (reg + std::abs(static_cast<int>(kVRegNonSpecialTempBaseReg)) - 1) * sizeof(uint32_t);
return locals_start + relative_offset;
} else if (reg < num_regs) {
return locals_start + (reg * sizeof(uint32_t));
} else {
// Handle ins.
- return frame_size + ((reg - num_regs) * sizeof(uint32_t)) + sizeof(StackReference<mirror::ArtMethod>);
+ return frame_size + ((reg - num_regs) * sizeof(uint32_t)) + GetBytesPerGprSpillLocation(isa);
}
}
- static int GetOutVROffset(uint16_t out_num) {
+ static int GetOutVROffset(uint16_t out_num, InstructionSet isa) {
// According to stack model, the first out is above the Method ptr.
- return sizeof(StackReference<mirror::ArtMethod>) + (out_num * sizeof(uint32_t));
+ return GetBytesPerGprSpillLocation(isa) + (out_num * sizeof(uint32_t));
}
uintptr_t GetCurrentQuickFramePc() const {
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 7470670..e67a64f 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -1018,7 +1018,8 @@
tls32_.state_and_flags.as_struct.flags = 0;
tls32_.state_and_flags.as_struct.state = kNative;
memset(&tlsPtr_.held_mutexes[0], 0, sizeof(tlsPtr_.held_mutexes));
- std::fill(tlsPtr_.rosalloc_runs, tlsPtr_.rosalloc_runs + kRosAllocNumOfSizeBrackets,
+ std::fill(tlsPtr_.rosalloc_runs,
+ tlsPtr_.rosalloc_runs + gc::allocator::RosAlloc::kNumThreadLocalSizeBrackets,
gc::allocator::RosAlloc::GetDedicatedFullRun());
for (uint32_t i = 0; i < kMaxCheckpoints; ++i) {
tlsPtr_.checkpoint_functions[i] = nullptr;
diff --git a/runtime/thread.h b/runtime/thread.h
index f869285..8c17082 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -29,6 +29,7 @@
#include "entrypoints/jni/jni_entrypoints.h"
#include "entrypoints/portable/portable_entrypoints.h"
#include "entrypoints/quick/quick_entrypoints.h"
+#include "gc/allocator/rosalloc.h"
#include "globals.h"
#include "jvalue.h"
#include "object_callbacks.h"
@@ -783,9 +784,6 @@
return tlsPtr_.thread_local_objects;
}
- // ROS alloc TLS.
- static constexpr size_t kRosAllocNumOfSizeBrackets = 34;
-
void* GetRosAllocRun(size_t index) const {
return tlsPtr_.rosalloc_runs[index];
}
@@ -1060,12 +1058,8 @@
byte* thread_local_end;
size_t thread_local_objects;
- // Thread-local rosalloc runs. There are 34 size brackets in rosalloc
- // runs (RosAlloc::kNumOfSizeBrackets). We can't refer to the
- // RosAlloc class due to a header file circular dependency issue.
- // To compensate, we check that the two values match at RosAlloc
- // initialization time.
- void* rosalloc_runs[kRosAllocNumOfSizeBrackets];
+ // There are RosAlloc::kNumThreadLocalSizeBrackets thread-local size brackets per thread.
+ void* rosalloc_runs[gc::allocator::RosAlloc::kNumThreadLocalSizeBrackets];
// Thread-local allocation stack data/routines.
mirror::Object** thread_local_alloc_stack_top;
diff --git a/test/JniTest/JniTest.java b/test/JniTest/JniTest.java
index d53cf5e..3c4ed35 100644
--- a/test/JniTest/JniTest.java
+++ b/test/JniTest/JniTest.java
@@ -24,6 +24,10 @@
testCallStaticVoidMethodOnSubClass();
testGetMirandaMethod();
testZeroLengthByteBuffers();
+ testByteMethod();
+ testShortMethod();
+ testBooleanMethod();
+ testCharMethod();
}
private static native void testFindClassOnAttachedNativeThread();
@@ -79,4 +83,67 @@
private static interface testGetMirandaMethod_MirandaInterface {
public boolean inInterface();
}
+
+ // Test sign-extension for values < 32b
+
+ native static byte byteMethod(byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7,
+ byte b8, byte b9, byte b10);
+
+ private static void testByteMethod() {
+ byte returns[] = { 0, 1, 2, 127, -1, -2, -128 };
+ for (int i = 0; i < returns.length; i++) {
+ byte result = byteMethod((byte)i, (byte)2, (byte)(-3), (byte)4, (byte)(-5), (byte)6,
+ (byte)(-7), (byte)8, (byte)(-9), (byte)10);
+ if (returns[i] != result) {
+ System.out.println("Run " + i + " with " + returns[i] + " vs " + result);
+ throw new AssertionError();
+ }
+ }
+ }
+
+ native static short shortMethod(short s1, short s2, short s3, short s4, short s5, short s6, short s7,
+ short s8, short s9, short s10);
+
+ private static void testShortMethod() {
+ short returns[] = { 0, 1, 2, 127, 32767, -1, -2, -128, -32768 };
+ for (int i = 0; i < returns.length; i++) {
+ short result = shortMethod((short)i, (short)2, (short)(-3), (short)4, (short)(-5), (short)6,
+ (short)(-7), (short)8, (short)(-9), (short)10);
+ if (returns[i] != result) {
+ System.out.println("Run " + i + " with " + returns[i] + " vs " + result);
+ throw new AssertionError();
+ }
+ }
+ }
+
+ // Test zero-extension for values < 32b
+
+ native static boolean booleanMethod(boolean b1, boolean b2, boolean b3, boolean b4, boolean b5, boolean b6, boolean b7,
+ boolean b8, boolean b9, boolean b10);
+
+ private static void testBooleanMethod() {
+ if (booleanMethod(false, true, false, true, false, true, false, true, false, true)) {
+ throw new AssertionError();
+ }
+
+ if (!booleanMethod(true, true, false, true, false, true, false, true, false, true)) {
+ throw new AssertionError();
+ }
+ }
+
+ native static char charMethod(char c1, char c2, char c3, char c4, char c5, char c6, char c7,
+ char c8, char c9, char c10);
+
+ private static void testCharMethod() {
+ char returns[] = { (char)0, (char)1, (char)2, (char)127, (char)255, (char)256, (char)15000,
+ (char)34000 };
+ for (int i = 0; i < returns.length; i++) {
+ char result = charMethod((char)i, 'a', 'b', 'c', '0', '1', '2', (char)1234, (char)2345,
+ (char)3456);
+ if (returns[i] != result) {
+ System.out.println("Run " + i + " with " + (int)returns[i] + " vs " + (int)result);
+ throw new AssertionError();
+ }
+ }
+ }
}
diff --git a/test/JniTest/jni_test.cc b/test/JniTest/jni_test.cc
index 33af94b..024ba53 100644
--- a/test/JniTest/jni_test.cc
+++ b/test/JniTest/jni_test.cc
@@ -137,3 +137,92 @@
assert(env->GetDirectBufferAddress(byte_buffer) == &buffer[0]);
assert(env->GetDirectBufferCapacity(byte_buffer) == 0);
}
+
+constexpr size_t kByteReturnSize = 7;
+jbyte byte_returns[kByteReturnSize] = { 0, 1, 2, 127, -1, -2, -128 };
+
+extern "C" jbyte JNICALL Java_JniTest_byteMethod(JNIEnv* env, jclass klass, jbyte b1, jbyte b2,
+ jbyte b3, jbyte b4, jbyte b5, jbyte b6,
+ jbyte b7, jbyte b8, jbyte b9, jbyte b10) {
+ // We use b1 to drive the output.
+ assert(b2 == 2);
+ assert(b3 == -3);
+ assert(b4 == 4);
+ assert(b5 == -5);
+ assert(b6 == 6);
+ assert(b7 == -7);
+ assert(b8 == 8);
+ assert(b9 == -9);
+ assert(b10 == 10);
+
+ assert(0 <= b1);
+ assert(b1 < static_cast<jbyte>(kByteReturnSize));
+
+ return byte_returns[b1];
+}
+
+constexpr size_t kShortReturnSize = 9;
+jshort short_returns[kShortReturnSize] = { 0, 1, 2, 127, 32767, -1, -2, -128,
+ static_cast<jshort>(0x8000) };
+// The weird static_cast is because short int is only guaranteed down to -32767, not Java's -32768.
+
+extern "C" jshort JNICALL Java_JniTest_shortMethod(JNIEnv* env, jclass klass, jshort s1, jshort s2,
+ jshort s3, jshort s4, jshort s5, jshort s6,
+ jshort s7, jshort s8, jshort s9, jshort s10) {
+ // We use s1 to drive the output.
+ assert(s2 == 2);
+ assert(s3 == -3);
+ assert(s4 == 4);
+ assert(s5 == -5);
+ assert(s6 == 6);
+ assert(s7 == -7);
+ assert(s8 == 8);
+ assert(s9 == -9);
+ assert(s10 == 10);
+
+ assert(0 <= s1);
+ assert(s1 < static_cast<jshort>(kShortReturnSize));
+
+ return short_returns[s1];
+}
+
+extern "C" jboolean JNICALL Java_JniTest_booleanMethod(JNIEnv* env, jclass klass, jboolean b1,
+ jboolean b2, jboolean b3, jboolean b4,
+ jboolean b5, jboolean b6, jboolean b7,
+ jboolean b8, jboolean b9, jboolean b10) {
+ // We use b1 to drive the output.
+ assert(b2 == JNI_TRUE);
+ assert(b3 == JNI_FALSE);
+ assert(b4 == JNI_TRUE);
+ assert(b5 == JNI_FALSE);
+ assert(b6 == JNI_TRUE);
+ assert(b7 == JNI_FALSE);
+ assert(b8 == JNI_TRUE);
+ assert(b9 == JNI_FALSE);
+ assert(b10 == JNI_TRUE);
+
+ assert(b1 == JNI_TRUE || b1 == JNI_FALSE);
+ return b1;
+}
+
+constexpr size_t kCharReturnSize = 8;
+jchar char_returns[kCharReturnSize] = { 0, 1, 2, 127, 255, 256, 15000, 34000 };
+
+extern "C" jchar JNICALL Java_JniTest_charMethod(JNIEnv* env, jclass klacc, jchar c1, jchar c2,
+ jchar c3, jchar c4, jchar c5, jchar c6,
+ jchar c7, jchar c8, jchar c9, jchar c10) {
+ // We use c1 to drive the output.
+ assert(c2 == 'a');
+ assert(c3 == 'b');
+ assert(c4 == 'c');
+ assert(c5 == '0');
+ assert(c6 == '1');
+ assert(c7 == '2');
+ assert(c8 == 1234);
+ assert(c9 == 2345);
+ assert(c10 == 3456);
+
+ assert(c1 < static_cast<jchar>(kCharReturnSize));
+
+ return char_returns[c1];
+}
diff --git a/test/etc/push-and-run-test-jar b/test/etc/push-and-run-test-jar
index 93d7e79..e0d2f1d 100755
--- a/test/etc/push-and-run-test-jar
+++ b/test/etc/push-and-run-test-jar
@@ -140,7 +140,7 @@
fi
if [ "$GDB" = "y" ]; then
- gdb="/data/gdbserver$TARGET_SUFFIX :5039"
+ gdb="gdbserver$TARGET_SUFFIX :5039"
gdbargs="$exe"
fi
@@ -150,7 +150,7 @@
JNI_OPTS="-Xjnigreflimit:512 -Xcheck:jni"
-cmdline="cd $DEX_LOCATION && mkdir dalvik-cache && export ANDROID_DATA=$DEX_LOCATION && export DEX_LOCATION=$DEX_LOCATION && \
+cmdline="cd $DEX_LOCATION && mkdir -p dalvik-cache/{arm,arm64,mips,x86,x86_64} && export ANDROID_DATA=$DEX_LOCATION && export DEX_LOCATION=$DEX_LOCATION && \
$INVOKE_WITH $gdb /system/bin/dalvikvm$TARGET_SUFFIX $FLAGS $gdbargs -XXlib:$LIB $ZYGOTE $JNI_OPTS $INT_OPTS $DEBUGGER_OPTS $BOOT_OPT -cp $DEX_LOCATION/$TEST_NAME.jar Main"
if [ "$DEV_MODE" = "y" ]; then
echo $cmdline "$@"
diff --git a/tools/art b/tools/art
index c9c0d4f..e3f409c 100755
--- a/tools/art
+++ b/tools/art
@@ -48,7 +48,7 @@
ANDROID_HOST_OUT=$PROG_DIR/..
ANDROID_DATA=$PWD/android-data$$
-mkdir -p $ANDROID_DATA/dalvik-cache
+mkdir -p $ANDROID_DATA/dalvik-cache/{x86,x86_64}
ANDROID_DATA=$ANDROID_DATA \
ANDROID_ROOT=$ANDROID_HOST_OUT \
LD_LIBRARY_PATH=$ANDROID_HOST_OUT/lib \