diff options
29 files changed, 1066 insertions, 397 deletions
diff --git a/compiler/linker/arm64/relative_patcher_arm64.cc b/compiler/linker/arm64/relative_patcher_arm64.cc index 79e1785e91..9ddf200237 100644 --- a/compiler/linker/arm64/relative_patcher_arm64.cc +++ b/compiler/linker/arm64/relative_patcher_arm64.cc @@ -31,9 +31,7 @@ namespace linker { namespace { inline bool IsAdrpPatch(const LinkerPatch& patch) { - LinkerPatch::Type type = patch.GetType(); - return - (type == LinkerPatch::Type::kStringRelative || type == LinkerPatch::Type::kDexCacheArray) && + return (patch.IsPcRelative() && patch.GetType() != LinkerPatch::Type::kCallRelative) && patch.LiteralOffset() == patch.PcInsnOffset(); } @@ -214,11 +212,11 @@ void Arm64RelativePatcher::PatchPcRelativeReference(std::vector<uint8_t>* code, DCHECK(patch.GetType() == LinkerPatch::Type::kStringRelative || patch.GetType() == LinkerPatch::Type::kTypeRelative) << patch.GetType(); } else { - // With the read barrier (non-Baker) enabled, it could be kDexCacheArray in the - // HLoadString::LoadKind::kDexCachePcRelative case of VisitLoadString(). + // With the read barrier (non-Baker) enabled, it could be kStringBssEntry or kTypeBssEntry. DCHECK(patch.GetType() == LinkerPatch::Type::kStringRelative || patch.GetType() == LinkerPatch::Type::kTypeRelative || - patch.GetType() == LinkerPatch::Type::kDexCacheArray) << patch.GetType(); + patch.GetType() == LinkerPatch::Type::kStringBssEntry || + patch.GetType() == LinkerPatch::Type::kTypeBssEntry) << patch.GetType(); } shift = 0u; // No shift for ADD. } else { diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc index 20cdae3619..759a951d6b 100644 --- a/compiler/optimizing/code_generator_arm.cc +++ b/compiler/optimizing/code_generator_arm.cc @@ -367,22 +367,37 @@ class BoundsCheckSlowPathARM : public SlowPathCodeARM { class LoadClassSlowPathARM : public SlowPathCodeARM { public: - LoadClassSlowPathARM(HLoadClass* cls, - HInstruction* at, - uint32_t dex_pc, - bool do_clinit) + LoadClassSlowPathARM(HLoadClass* cls, HInstruction* at, uint32_t dex_pc, bool do_clinit) : SlowPathCodeARM(at), cls_(cls), dex_pc_(dex_pc), do_clinit_(do_clinit) { DCHECK(at->IsLoadClass() || at->IsClinitCheck()); } void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { LocationSummary* locations = instruction_->GetLocations(); + Location out = locations->Out(); + constexpr bool call_saves_everything_except_r0 = (!kUseReadBarrier || kUseBakerReadBarrier); CodeGeneratorARM* arm_codegen = down_cast<CodeGeneratorARM*>(codegen); __ Bind(GetEntryLabel()); SaveLiveRegisters(codegen, locations); InvokeRuntimeCallingConvention calling_convention; + // For HLoadClass/kBssEntry/kSaveEverything, make sure we preserve the address of the entry. + DCHECK_EQ(instruction_->IsLoadClass(), cls_ == instruction_); + bool is_load_class_bss_entry = + (cls_ == instruction_) && (cls_->GetLoadKind() == HLoadClass::LoadKind::kBssEntry); + Register entry_address = kNoRegister; + if (is_load_class_bss_entry && call_saves_everything_except_r0) { + Register temp = locations->GetTemp(0).AsRegister<Register>(); + // In the unlucky case that the `temp` is R0, we preserve the address in `out` across + // the kSaveEverything call. + bool temp_is_r0 = (temp == calling_convention.GetRegisterAt(0)); + entry_address = temp_is_r0 ? out.AsRegister<Register>() : temp; + DCHECK_NE(entry_address, calling_convention.GetRegisterAt(0)); + if (temp_is_r0) { + __ mov(entry_address, ShifterOperand(temp)); + } + } dex::TypeIndex type_index = cls_->GetTypeIndex(); __ LoadImmediate(calling_convention.GetRegisterAt(0), type_index.index_); QuickEntrypointEnum entrypoint = do_clinit_ ? kQuickInitializeStaticStorage @@ -394,30 +409,31 @@ class LoadClassSlowPathARM : public SlowPathCodeARM { CheckEntrypointTypes<kQuickInitializeType, void*, uint32_t>(); } + // For HLoadClass/kBssEntry, store the resolved Class to the BSS entry. + if (is_load_class_bss_entry) { + if (call_saves_everything_except_r0) { + // The class entry address was preserved in `entry_address` thanks to kSaveEverything. + __ str(R0, Address(entry_address)); + } else { + // For non-Baker read barrier, we need to re-calculate the address of the string entry. + Register temp = IP; + CodeGeneratorARM::PcRelativePatchInfo* labels = + arm_codegen->NewTypeBssEntryPatch(cls_->GetDexFile(), type_index); + __ BindTrackedLabel(&labels->movw_label); + __ movw(temp, /* placeholder */ 0u); + __ BindTrackedLabel(&labels->movt_label); + __ movt(temp, /* placeholder */ 0u); + __ BindTrackedLabel(&labels->add_pc_label); + __ add(temp, temp, ShifterOperand(PC)); + __ str(R0, Address(temp)); + } + } // Move the class to the desired location. - Location out = locations->Out(); if (out.IsValid()) { DCHECK(out.IsRegister() && !locations->GetLiveRegisters()->ContainsCoreRegister(out.reg())); arm_codegen->Move32(locations->Out(), Location::RegisterLocation(R0)); } RestoreLiveRegisters(codegen, locations); - // For HLoadClass/kBssEntry, store the resolved Class to the BSS entry. - DCHECK_EQ(instruction_->IsLoadClass(), cls_ == instruction_); - if (cls_ == instruction_ && cls_->GetLoadKind() == HLoadClass::LoadKind::kBssEntry) { - DCHECK(out.IsValid()); - // TODO: Change art_quick_initialize_type/art_quick_initialize_static_storage to - // kSaveEverything and use a temporary for the .bss entry address in the fast path, - // so that we can avoid another calculation here. - CodeGeneratorARM::PcRelativePatchInfo* labels = - arm_codegen->NewTypeBssEntryPatch(cls_->GetDexFile(), type_index); - __ BindTrackedLabel(&labels->movw_label); - __ movw(IP, /* placeholder */ 0u); - __ BindTrackedLabel(&labels->movt_label); - __ movt(IP, /* placeholder */ 0u); - __ BindTrackedLabel(&labels->add_pc_label); - __ add(IP, IP, ShifterOperand(PC)); - __ str(locations->Out().AsRegister<Register>(), Address(IP)); - } __ b(GetExitLabel()); } @@ -441,12 +457,13 @@ class LoadStringSlowPathARM : public SlowPathCodeARM { explicit LoadStringSlowPathARM(HLoadString* instruction) : SlowPathCodeARM(instruction) {} void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { + DCHECK(instruction_->IsLoadString()); + DCHECK_EQ(instruction_->AsLoadString()->GetLoadKind(), HLoadString::LoadKind::kBssEntry); LocationSummary* locations = instruction_->GetLocations(); DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(locations->Out().reg())); HLoadString* load = instruction_->AsLoadString(); const dex::StringIndex string_index = load->GetStringIndex(); Register out = locations->Out().AsRegister<Register>(); - Register temp = locations->GetTemp(0).AsRegister<Register>(); constexpr bool call_saves_everything_except_r0 = (!kUseReadBarrier || kUseBakerReadBarrier); CodeGeneratorARM* arm_codegen = down_cast<CodeGeneratorARM*>(codegen); @@ -455,12 +472,16 @@ class LoadStringSlowPathARM : public SlowPathCodeARM { InvokeRuntimeCallingConvention calling_convention; // In the unlucky case that the `temp` is R0, we preserve the address in `out` across - // the kSaveEverything call (or use `out` for the address after non-kSaveEverything call). - bool temp_is_r0 = (temp == calling_convention.GetRegisterAt(0)); - Register entry_address = temp_is_r0 ? out : temp; - DCHECK_NE(entry_address, calling_convention.GetRegisterAt(0)); - if (call_saves_everything_except_r0 && temp_is_r0) { - __ mov(entry_address, ShifterOperand(temp)); + // the kSaveEverything call. + Register entry_address = kNoRegister; + if (call_saves_everything_except_r0) { + Register temp = locations->GetTemp(0).AsRegister<Register>(); + bool temp_is_r0 = (temp == calling_convention.GetRegisterAt(0)); + entry_address = temp_is_r0 ? out : temp; + DCHECK_NE(entry_address, calling_convention.GetRegisterAt(0)); + if (temp_is_r0) { + __ mov(entry_address, ShifterOperand(temp)); + } } __ LoadImmediate(calling_convention.GetRegisterAt(0), string_index.index_); @@ -473,15 +494,16 @@ class LoadStringSlowPathARM : public SlowPathCodeARM { __ str(R0, Address(entry_address)); } else { // For non-Baker read barrier, we need to re-calculate the address of the string entry. + Register temp = IP; CodeGeneratorARM::PcRelativePatchInfo* labels = arm_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index); __ BindTrackedLabel(&labels->movw_label); - __ movw(entry_address, /* placeholder */ 0u); + __ movw(temp, /* placeholder */ 0u); __ BindTrackedLabel(&labels->movt_label); - __ movt(entry_address, /* placeholder */ 0u); + __ movt(temp, /* placeholder */ 0u); __ BindTrackedLabel(&labels->add_pc_label); - __ add(entry_address, entry_address, ShifterOperand(PC)); - __ str(R0, Address(entry_address)); + __ add(temp, temp, ShifterOperand(PC)); + __ str(R0, Address(temp)); } arm_codegen->Move32(locations->Out(), Location::RegisterLocation(R0)); @@ -624,6 +646,10 @@ class ArraySetSlowPathARM : public SlowPathCodeARM { // probably still be a from-space reference (unless it gets updated by // another thread, or if another thread installed another object // reference (different from `ref`) in `obj.field`). +// +// If `entrypoint` is a valid location it is assumed to already be +// holding the entrypoint. The case where the entrypoint is passed in +// is for the GcRoot read barrier. class ReadBarrierMarkSlowPathARM : public SlowPathCodeARM { public: ReadBarrierMarkSlowPathARM(HInstruction* instruction, @@ -5755,6 +5781,7 @@ void LocationsBuilderARM::VisitLoadClass(HLoadClass* cls) { cls, Location::RegisterLocation(calling_convention.GetRegisterAt(0)), Location::RegisterLocation(R0)); + DCHECK_EQ(calling_convention.GetRegisterAt(0), R0); return; } DCHECK(!cls->NeedsAccessCheck()); @@ -5772,6 +5799,22 @@ void LocationsBuilderARM::VisitLoadClass(HLoadClass* cls) { locations->SetInAt(0, Location::RequiresRegister()); } locations->SetOut(Location::RequiresRegister()); + if (load_kind == HLoadClass::LoadKind::kBssEntry) { + if (!kUseReadBarrier || kUseBakerReadBarrier) { + // Rely on the type resolution or initialization and marking to save everything we need. + // Note that IP may be clobbered by saving/restoring the live register (only one thanks + // to the custom calling convention) or by marking, so we request a different temp. + locations->AddTemp(Location::RequiresRegister()); + RegisterSet caller_saves = RegisterSet::Empty(); + InvokeRuntimeCallingConvention calling_convention; + caller_saves.Add(Location::RegisterLocation(calling_convention.GetRegisterAt(0))); + // TODO: Add GetReturnLocation() to the calling convention so that we can DCHECK() + // that the the kPrimNot result register is the same as the first argument register. + locations->SetCustomSlowPathCallerSaves(caller_saves); + } else { + // For non-Baker read barrier we have a temp-clobbering call. + } + } } // NO_THREAD_SAFETY_ANALYSIS as we manipulate handles whose internal object we know does not @@ -5834,15 +5877,18 @@ void InstructionCodeGeneratorARM::VisitLoadClass(HLoadClass* cls) NO_THREAD_SAFE break; } case HLoadClass::LoadKind::kBssEntry: { + Register temp = (!kUseReadBarrier || kUseBakerReadBarrier) + ? locations->GetTemp(0).AsRegister<Register>() + : out; CodeGeneratorARM::PcRelativePatchInfo* labels = codegen_->NewTypeBssEntryPatch(cls->GetDexFile(), cls->GetTypeIndex()); __ BindTrackedLabel(&labels->movw_label); - __ movw(out, /* placeholder */ 0u); + __ movw(temp, /* placeholder */ 0u); __ BindTrackedLabel(&labels->movt_label); - __ movt(out, /* placeholder */ 0u); + __ movt(temp, /* placeholder */ 0u); __ BindTrackedLabel(&labels->add_pc_label); - __ add(out, out, ShifterOperand(PC)); - GenerateGcRootFieldLoad(cls, out_loc, out, 0, kCompilerReadBarrierOption); + __ add(temp, temp, ShifterOperand(PC)); + GenerateGcRootFieldLoad(cls, out_loc, temp, /* offset */ 0, read_barrier_option); generate_null_check = true; break; } @@ -5851,7 +5897,7 @@ void InstructionCodeGeneratorARM::VisitLoadClass(HLoadClass* cls) NO_THREAD_SAFE cls->GetTypeIndex(), cls->GetClass())); // /* GcRoot<mirror::Class> */ out = *out - GenerateGcRootFieldLoad(cls, out_loc, out, /* offset */ 0, kCompilerReadBarrierOption); + GenerateGcRootFieldLoad(cls, out_loc, out, /* offset */ 0, read_barrier_option); break; } case HLoadClass::LoadKind::kDexCacheViaMethod: @@ -5938,9 +5984,9 @@ void LocationsBuilderARM::VisitLoadString(HLoadString* load) { locations->SetOut(Location::RequiresRegister()); if (load_kind == HLoadString::LoadKind::kBssEntry) { if (!kUseReadBarrier || kUseBakerReadBarrier) { - // Rely on the pResolveString and/or marking to save everything, including temps. - // Note that IP may theoretically be clobbered by saving/restoring the live register - // (only one thanks to the custom calling convention), so we request a different temp. + // Rely on the pResolveString and marking to save everything we need, including temps. + // Note that IP may be clobbered by saving/restoring the live register (only one thanks + // to the custom calling convention) or by marking, so we request a different temp. locations->AddTemp(Location::RequiresRegister()); RegisterSet caller_saves = RegisterSet::Empty(); InvokeRuntimeCallingConvention calling_convention; @@ -5991,7 +6037,9 @@ void InstructionCodeGeneratorARM::VisitLoadString(HLoadString* load) NO_THREAD_S } case HLoadString::LoadKind::kBssEntry: { DCHECK(!codegen_->GetCompilerOptions().IsBootImage()); - Register temp = locations->GetTemp(0).AsRegister<Register>(); + Register temp = (!kUseReadBarrier || kUseBakerReadBarrier) + ? locations->GetTemp(0).AsRegister<Register>() + : out; CodeGeneratorARM::PcRelativePatchInfo* labels = codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex()); __ BindTrackedLabel(&labels->movw_label); diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index 598be4715b..27b42536ef 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -275,15 +275,37 @@ class LoadClassSlowPathARM64 : public SlowPathCodeARM64 { LoadClassSlowPathARM64(HLoadClass* cls, HInstruction* at, uint32_t dex_pc, - bool do_clinit) - : SlowPathCodeARM64(at), cls_(cls), dex_pc_(dex_pc), do_clinit_(do_clinit) { + bool do_clinit, + vixl::aarch64::Register bss_entry_temp = vixl::aarch64::Register(), + vixl::aarch64::Label* bss_entry_adrp_label = nullptr) + : SlowPathCodeARM64(at), + cls_(cls), + dex_pc_(dex_pc), + do_clinit_(do_clinit), + bss_entry_temp_(bss_entry_temp), + bss_entry_adrp_label_(bss_entry_adrp_label) { DCHECK(at->IsLoadClass() || at->IsClinitCheck()); } void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { LocationSummary* locations = instruction_->GetLocations(); + Location out = locations->Out(); + constexpr bool call_saves_everything_except_r0_ip0 = (!kUseReadBarrier || kUseBakerReadBarrier); CodeGeneratorARM64* arm64_codegen = down_cast<CodeGeneratorARM64*>(codegen); + // For HLoadClass/kBssEntry/kSaveEverything, make sure we preserve the page address of + // the entry which is in a scratch register. Make sure it's not used for saving/restoring + // registers. Exclude the scratch register also for non-Baker read barrier for simplicity. + DCHECK_EQ(instruction_->IsLoadClass(), cls_ == instruction_); + bool is_load_class_bss_entry = + (cls_ == instruction_) && (cls_->GetLoadKind() == HLoadClass::LoadKind::kBssEntry); + UseScratchRegisterScope temps(arm64_codegen->GetVIXLAssembler()); + if (is_load_class_bss_entry) { + // This temp is a scratch register. + DCHECK(bss_entry_temp_.IsValid()); + temps.Exclude(bss_entry_temp_); + } + __ Bind(GetEntryLabel()); SaveLiveRegisters(codegen, locations); @@ -300,7 +322,6 @@ class LoadClassSlowPathARM64 : public SlowPathCodeARM64 { } // Move the class to the desired location. - Location out = locations->Out(); if (out.IsValid()) { DCHECK(out.IsRegister() && !locations->GetLiveRegisters()->ContainsCoreRegister(out.reg())); Primitive::Type type = instruction_->GetType(); @@ -308,25 +329,23 @@ class LoadClassSlowPathARM64 : public SlowPathCodeARM64 { } RestoreLiveRegisters(codegen, locations); // For HLoadClass/kBssEntry, store the resolved Class to the BSS entry. - DCHECK_EQ(instruction_->IsLoadClass(), cls_ == instruction_); - if (cls_ == instruction_ && cls_->GetLoadKind() == HLoadClass::LoadKind::kBssEntry) { + if (is_load_class_bss_entry) { DCHECK(out.IsValid()); - UseScratchRegisterScope temps(arm64_codegen->GetVIXLAssembler()); - Register temp = temps.AcquireX(); const DexFile& dex_file = cls_->GetDexFile(); - // TODO: Change art_quick_initialize_type/art_quick_initialize_static_storage to - // kSaveEverything and use a temporary for the ADRP in the fast path, so that we - // can avoid the ADRP here. - vixl::aarch64::Label* adrp_label = - arm64_codegen->NewBssEntryTypePatch(dex_file, type_index); - arm64_codegen->EmitAdrpPlaceholder(adrp_label, temp); + if (call_saves_everything_except_r0_ip0) { + // The class entry page address was preserved in bss_entry_temp_ thanks to kSaveEverything. + } else { + // For non-Baker read barrier, we need to re-calculate the address of the class entry page. + bss_entry_adrp_label_ = arm64_codegen->NewBssEntryTypePatch(dex_file, type_index); + arm64_codegen->EmitAdrpPlaceholder(bss_entry_adrp_label_, bss_entry_temp_); + } vixl::aarch64::Label* strp_label = - arm64_codegen->NewBssEntryTypePatch(dex_file, type_index, adrp_label); + arm64_codegen->NewBssEntryTypePatch(dex_file, type_index, bss_entry_adrp_label_); { SingleEmissionCheckScope guard(arm64_codegen->GetVIXLAssembler()); __ Bind(strp_label); __ str(RegisterFrom(locations->Out(), Primitive::kPrimNot), - MemOperand(temp, /* offset placeholder */ 0)); + MemOperand(bss_entry_temp_, /* offset placeholder */ 0)); } } __ B(GetExitLabel()); @@ -344,6 +363,10 @@ class LoadClassSlowPathARM64 : public SlowPathCodeARM64 { // Whether to initialize the class. const bool do_clinit_; + // For HLoadClass/kBssEntry, the temp register and the label of the ADRP where it was loaded. + vixl::aarch64::Register bss_entry_temp_; + vixl::aarch64::Label* bss_entry_adrp_label_; + DISALLOW_COPY_AND_ASSIGN(LoadClassSlowPathARM64); }; @@ -619,8 +642,10 @@ void JumpTableARM64::EmitTable(CodeGeneratorARM64* codegen) { // probably still be a from-space reference (unless it gets updated by // another thread, or if another thread installed another object // reference (different from `ref`) in `obj.field`). -// If entrypoint is a valid location it is assumed to already be holding the entrypoint. The case -// where the entrypoint is passed in is for the GcRoot read barrier. +// +// If `entrypoint` is a valid location it is assumed to already be +// holding the entrypoint. The case where the entrypoint is passed in +// is for the GcRoot read barrier. class ReadBarrierMarkSlowPathARM64 : public SlowPathCodeARM64 { public: ReadBarrierMarkSlowPathARM64(HInstruction* instruction, @@ -4393,6 +4418,7 @@ void LocationsBuilderARM64::VisitLoadClass(HLoadClass* cls) { cls, LocationFrom(calling_convention.GetRegisterAt(0)), LocationFrom(vixl::aarch64::x0)); + DCHECK(calling_convention.GetRegisterAt(0).Is(vixl::aarch64::x0)); return; } DCHECK(!cls->NeedsAccessCheck()); @@ -4410,6 +4436,22 @@ void LocationsBuilderARM64::VisitLoadClass(HLoadClass* cls) { locations->SetInAt(0, Location::RequiresRegister()); } locations->SetOut(Location::RequiresRegister()); + if (cls->GetLoadKind() == HLoadClass::LoadKind::kBssEntry) { + if (!kUseReadBarrier || kUseBakerReadBarrier) { + // Rely on the type resolution or initialization and marking to save everything we need. + // Note that IP0 may be clobbered by saving/restoring the live register (only one thanks + // to the custom calling convention) or by marking, so we shall use IP1. + RegisterSet caller_saves = RegisterSet::Empty(); + InvokeRuntimeCallingConvention calling_convention; + caller_saves.Add(Location::RegisterLocation(calling_convention.GetRegisterAt(0).GetCode())); + DCHECK_EQ(calling_convention.GetRegisterAt(0).GetCode(), + RegisterFrom(calling_convention.GetReturnLocation(Primitive::kPrimNot), + Primitive::kPrimNot).GetCode()); + locations->SetCustomSlowPathCallerSaves(caller_saves); + } else { + // For non-Baker read barrier we have a temp-clobbering call. + } + } } // NO_THREAD_SAFETY_ANALYSIS as we manipulate handles whose internal object we know does not @@ -4424,6 +4466,8 @@ void InstructionCodeGeneratorARM64::VisitLoadClass(HLoadClass* cls) NO_THREAD_SA Location out_loc = cls->GetLocations()->Out(); Register out = OutputRegister(cls); + Register bss_entry_temp; + vixl::aarch64::Label* bss_entry_adrp_label = nullptr; const ReadBarrierOption read_barrier_option = cls->IsInBootImage() ? kWithoutReadBarrier @@ -4473,18 +4517,23 @@ void InstructionCodeGeneratorARM64::VisitLoadClass(HLoadClass* cls) NO_THREAD_SA // Add ADRP with its PC-relative Class .bss entry patch. const DexFile& dex_file = cls->GetDexFile(); dex::TypeIndex type_index = cls->GetTypeIndex(); - vixl::aarch64::Label* adrp_label = codegen_->NewBssEntryTypePatch(dex_file, type_index); - codegen_->EmitAdrpPlaceholder(adrp_label, out.X()); + // We can go to slow path even with non-zero reference and in that case marking + // can clobber IP0, so we need to use IP1 which shall be preserved. + bss_entry_temp = ip1; + UseScratchRegisterScope temps(codegen_->GetVIXLAssembler()); + temps.Exclude(bss_entry_temp); + bss_entry_adrp_label = codegen_->NewBssEntryTypePatch(dex_file, type_index); + codegen_->EmitAdrpPlaceholder(bss_entry_adrp_label, bss_entry_temp); // Add LDR with its PC-relative Class patch. vixl::aarch64::Label* ldr_label = - codegen_->NewBssEntryTypePatch(dex_file, type_index, adrp_label); + codegen_->NewBssEntryTypePatch(dex_file, type_index, bss_entry_adrp_label); // /* GcRoot<mirror::Class> */ out = *(base_address + offset) /* PC-relative */ GenerateGcRootFieldLoad(cls, - cls->GetLocations()->Out(), - out.X(), - /* placeholder */ 0u, + out_loc, + bss_entry_temp, + /* offset placeholder */ 0u, ldr_label, - kCompilerReadBarrierOption); + read_barrier_option); generate_null_check = true; break; } @@ -4497,7 +4546,7 @@ void InstructionCodeGeneratorARM64::VisitLoadClass(HLoadClass* cls) NO_THREAD_SA out.X(), /* offset */ 0, /* fixup_label */ nullptr, - kCompilerReadBarrierOption); + read_barrier_option); break; } case HLoadClass::LoadKind::kDexCacheViaMethod: @@ -4506,10 +4555,11 @@ void InstructionCodeGeneratorARM64::VisitLoadClass(HLoadClass* cls) NO_THREAD_SA UNREACHABLE(); } - if (generate_null_check || cls->MustGenerateClinitCheck()) { + bool do_clinit = cls->MustGenerateClinitCheck(); + if (generate_null_check || do_clinit) { DCHECK(cls->CanCallRuntime()); SlowPathCodeARM64* slow_path = new (GetGraph()->GetArena()) LoadClassSlowPathARM64( - cls, cls, cls->GetDexPc(), cls->MustGenerateClinitCheck()); + cls, cls, cls->GetDexPc(), do_clinit, bss_entry_temp, bss_entry_adrp_label); codegen_->AddSlowPath(slow_path); if (generate_null_check) { __ Cbz(out, slow_path->GetEntryLabel()); @@ -4577,7 +4627,9 @@ void LocationsBuilderARM64::VisitLoadString(HLoadString* load) { locations->SetOut(Location::RequiresRegister()); if (load->GetLoadKind() == HLoadString::LoadKind::kBssEntry) { if (!kUseReadBarrier || kUseBakerReadBarrier) { - // Rely on the pResolveString and/or marking to save everything, including temps. + // Rely on the pResolveString and marking to save everything we need. + // Note that IP0 may be clobbered by saving/restoring the live register (only one thanks + // to the custom calling convention) or by marking, so we shall use IP1. RegisterSet caller_saves = RegisterSet::Empty(); InvokeRuntimeCallingConvention calling_convention; caller_saves.Add(Location::RegisterLocation(calling_convention.GetRegisterAt(0).GetCode())); @@ -4628,8 +4680,11 @@ void InstructionCodeGeneratorARM64::VisitLoadString(HLoadString* load) NO_THREAD const DexFile& dex_file = load->GetDexFile(); const dex::StringIndex string_index = load->GetStringIndex(); DCHECK(!codegen_->GetCompilerOptions().IsBootImage()); + // We could use IP0 as the marking shall not clobber IP0 if the reference is null and + // that's when we need the slow path. But let's not rely on such details and use IP1. + Register temp = ip1; UseScratchRegisterScope temps(codegen_->GetVIXLAssembler()); - Register temp = temps.AcquireX(); + temps.Exclude(temp); vixl::aarch64::Label* adrp_label = codegen_->NewPcRelativeStringPatch(dex_file, string_index); codegen_->EmitAdrpPlaceholder(adrp_label, temp); // Add LDR with its PC-relative String patch. diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc index e18960872e..5c4ca5bc17 100644 --- a/compiler/optimizing/code_generator_arm_vixl.cc +++ b/compiler/optimizing/code_generator_arm_vixl.cc @@ -400,12 +400,30 @@ class LoadClassSlowPathARMVIXL : public SlowPathCodeARMVIXL { void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { LocationSummary* locations = instruction_->GetLocations(); + Location out = locations->Out(); + constexpr bool call_saves_everything_except_r0 = (!kUseReadBarrier || kUseBakerReadBarrier); CodeGeneratorARMVIXL* arm_codegen = down_cast<CodeGeneratorARMVIXL*>(codegen); __ Bind(GetEntryLabel()); SaveLiveRegisters(codegen, locations); InvokeRuntimeCallingConventionARMVIXL calling_convention; + // For HLoadClass/kBssEntry/kSaveEverything, make sure we preserve the address of the entry. + DCHECK_EQ(instruction_->IsLoadClass(), cls_ == instruction_); + bool is_load_class_bss_entry = + (cls_ == instruction_) && (cls_->GetLoadKind() == HLoadClass::LoadKind::kBssEntry); + vixl32::Register entry_address; + if (is_load_class_bss_entry && call_saves_everything_except_r0) { + vixl32::Register temp = RegisterFrom(locations->GetTemp(0)); + // In the unlucky case that the `temp` is R0, we preserve the address in `out` across + // the kSaveEverything call. + bool temp_is_r0 = temp.Is(calling_convention.GetRegisterAt(0)); + entry_address = temp_is_r0 ? RegisterFrom(out) : temp; + DCHECK(!entry_address.Is(calling_convention.GetRegisterAt(0))); + if (temp_is_r0) { + __ Mov(entry_address, temp); + } + } dex::TypeIndex type_index = cls_->GetTypeIndex(); __ Mov(calling_convention.GetRegisterAt(0), type_index.index_); QuickEntrypointEnum entrypoint = do_clinit_ ? kQuickInitializeStaticStorage @@ -417,27 +435,28 @@ class LoadClassSlowPathARMVIXL : public SlowPathCodeARMVIXL { CheckEntrypointTypes<kQuickInitializeType, void*, uint32_t>(); } + // For HLoadClass/kBssEntry, store the resolved Class to the BSS entry. + if (is_load_class_bss_entry) { + if (call_saves_everything_except_r0) { + // The class entry address was preserved in `entry_address` thanks to kSaveEverything. + __ Str(r0, MemOperand(entry_address)); + } else { + // For non-Baker read barrier, we need to re-calculate the address of the string entry. + UseScratchRegisterScope temps( + down_cast<CodeGeneratorARMVIXL*>(codegen)->GetVIXLAssembler()); + vixl32::Register temp = temps.Acquire(); + CodeGeneratorARMVIXL::PcRelativePatchInfo* labels = + arm_codegen->NewTypeBssEntryPatch(cls_->GetDexFile(), type_index); + arm_codegen->EmitMovwMovtPlaceholder(labels, temp); + __ Str(r0, MemOperand(temp)); + } + } // Move the class to the desired location. - Location out = locations->Out(); if (out.IsValid()) { DCHECK(out.IsRegister() && !locations->GetLiveRegisters()->ContainsCoreRegister(out.reg())); arm_codegen->Move32(locations->Out(), LocationFrom(r0)); } RestoreLiveRegisters(codegen, locations); - // For HLoadClass/kBssEntry, store the resolved Class to the BSS entry. - DCHECK_EQ(instruction_->IsLoadClass(), cls_ == instruction_); - if (cls_ == instruction_ && cls_->GetLoadKind() == HLoadClass::LoadKind::kBssEntry) { - DCHECK(out.IsValid()); - // TODO: Change art_quick_initialize_type/art_quick_initialize_static_storage to - // kSaveEverything and use a temporary for the .bss entry address in the fast path, - // so that we can avoid another calculation here. - UseScratchRegisterScope temps(down_cast<CodeGeneratorARMVIXL*>(codegen)->GetVIXLAssembler()); - vixl32::Register temp = temps.Acquire(); - CodeGeneratorARMVIXL::PcRelativePatchInfo* labels = - arm_codegen->NewTypeBssEntryPatch(cls_->GetDexFile(), type_index); - arm_codegen->EmitMovwMovtPlaceholder(labels, temp); - __ Str(OutputRegister(cls_), MemOperand(temp)); - } __ B(GetExitLabel()); } @@ -462,12 +481,13 @@ class LoadStringSlowPathARMVIXL : public SlowPathCodeARMVIXL { : SlowPathCodeARMVIXL(instruction) {} void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { + DCHECK(instruction_->IsLoadString()); + DCHECK_EQ(instruction_->AsLoadString()->GetLoadKind(), HLoadString::LoadKind::kBssEntry); LocationSummary* locations = instruction_->GetLocations(); DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(locations->Out().reg())); HLoadString* load = instruction_->AsLoadString(); const dex::StringIndex string_index = load->GetStringIndex(); vixl32::Register out = OutputRegister(load); - vixl32::Register temp = RegisterFrom(locations->GetTemp(0)); constexpr bool call_saves_everything_except_r0 = (!kUseReadBarrier || kUseBakerReadBarrier); CodeGeneratorARMVIXL* arm_codegen = down_cast<CodeGeneratorARMVIXL*>(codegen); @@ -476,12 +496,16 @@ class LoadStringSlowPathARMVIXL : public SlowPathCodeARMVIXL { InvokeRuntimeCallingConventionARMVIXL calling_convention; // In the unlucky case that the `temp` is R0, we preserve the address in `out` across - // the kSaveEverything call (or use `out` for the address after non-kSaveEverything call). - bool temp_is_r0 = (temp.Is(calling_convention.GetRegisterAt(0))); - vixl32::Register entry_address = temp_is_r0 ? out : temp; - DCHECK(!entry_address.Is(calling_convention.GetRegisterAt(0))); - if (call_saves_everything_except_r0 && temp_is_r0) { - __ Mov(entry_address, temp); + // the kSaveEverything call. + vixl32::Register entry_address; + if (call_saves_everything_except_r0) { + vixl32::Register temp = RegisterFrom(locations->GetTemp(0)); + bool temp_is_r0 = (temp.Is(calling_convention.GetRegisterAt(0))); + entry_address = temp_is_r0 ? out : temp; + DCHECK(!entry_address.Is(calling_convention.GetRegisterAt(0))); + if (temp_is_r0) { + __ Mov(entry_address, temp); + } } __ Mov(calling_convention.GetRegisterAt(0), string_index.index_); @@ -494,10 +518,13 @@ class LoadStringSlowPathARMVIXL : public SlowPathCodeARMVIXL { __ Str(r0, MemOperand(entry_address)); } else { // For non-Baker read barrier, we need to re-calculate the address of the string entry. + UseScratchRegisterScope temps( + down_cast<CodeGeneratorARMVIXL*>(codegen)->GetVIXLAssembler()); + vixl32::Register temp = temps.Acquire(); CodeGeneratorARMVIXL::PcRelativePatchInfo* labels = arm_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index); - arm_codegen->EmitMovwMovtPlaceholder(labels, out); - __ Str(r0, MemOperand(entry_address)); + arm_codegen->EmitMovwMovtPlaceholder(labels, temp); + __ Str(r0, MemOperand(temp)); } arm_codegen->Move32(locations->Out(), LocationFrom(r0)); @@ -5832,6 +5859,7 @@ void LocationsBuilderARMVIXL::VisitLoadClass(HLoadClass* cls) { cls, LocationFrom(calling_convention.GetRegisterAt(0)), LocationFrom(r0)); + DCHECK(calling_convention.GetRegisterAt(0).Is(r0)); return; } DCHECK(!cls->NeedsAccessCheck()); @@ -5849,6 +5877,22 @@ void LocationsBuilderARMVIXL::VisitLoadClass(HLoadClass* cls) { locations->SetInAt(0, Location::RequiresRegister()); } locations->SetOut(Location::RequiresRegister()); + if (load_kind == HLoadClass::LoadKind::kBssEntry) { + if (!kUseReadBarrier || kUseBakerReadBarrier) { + // Rely on the type resolution or initialization and marking to save everything we need. + // Note that IP may be clobbered by saving/restoring the live register (only one thanks + // to the custom calling convention) or by marking, so we request a different temp. + locations->AddTemp(Location::RequiresRegister()); + RegisterSet caller_saves = RegisterSet::Empty(); + InvokeRuntimeCallingConventionARMVIXL calling_convention; + caller_saves.Add(LocationFrom(calling_convention.GetRegisterAt(0))); + // TODO: Add GetReturnLocation() to the calling convention so that we can DCHECK() + // that the the kPrimNot result register is the same as the first argument register. + locations->SetCustomSlowPathCallerSaves(caller_saves); + } else { + // For non-Baker read barrier we have a temp-clobbering call. + } + } } // NO_THREAD_SAFETY_ANALYSIS as we manipulate handles whose internal object we know does not @@ -5906,10 +5950,13 @@ void InstructionCodeGeneratorARMVIXL::VisitLoadClass(HLoadClass* cls) NO_THREAD_ break; } case HLoadClass::LoadKind::kBssEntry: { + vixl32::Register temp = (!kUseReadBarrier || kUseBakerReadBarrier) + ? RegisterFrom(locations->GetTemp(0)) + : out; CodeGeneratorARMVIXL::PcRelativePatchInfo* labels = codegen_->NewTypeBssEntryPatch(cls->GetDexFile(), cls->GetTypeIndex()); - codegen_->EmitMovwMovtPlaceholder(labels, out); - GenerateGcRootFieldLoad(cls, out_loc, out, 0, kCompilerReadBarrierOption); + codegen_->EmitMovwMovtPlaceholder(labels, temp); + GenerateGcRootFieldLoad(cls, out_loc, temp, /* offset */ 0, read_barrier_option); generate_null_check = true; break; } @@ -5918,7 +5965,7 @@ void InstructionCodeGeneratorARMVIXL::VisitLoadClass(HLoadClass* cls) NO_THREAD_ cls->GetTypeIndex(), cls->GetClass())); // /* GcRoot<mirror::Class> */ out = *out - GenerateGcRootFieldLoad(cls, out_loc, out, /* offset */ 0, kCompilerReadBarrierOption); + GenerateGcRootFieldLoad(cls, out_loc, out, /* offset */ 0, read_barrier_option); break; } case HLoadClass::LoadKind::kDexCacheViaMethod: @@ -6012,9 +6059,9 @@ void LocationsBuilderARMVIXL::VisitLoadString(HLoadString* load) { locations->SetOut(Location::RequiresRegister()); if (load_kind == HLoadString::LoadKind::kBssEntry) { if (!kUseReadBarrier || kUseBakerReadBarrier) { - // Rely on the pResolveString and/or marking to save everything, including temps. - // Note that IP may theoretically be clobbered by saving/restoring the live register - // (only one thanks to the custom calling convention), so we request a different temp. + // Rely on the pResolveString and marking to save everything we need, including temps. + // Note that IP may be clobbered by saving/restoring the live register (only one thanks + // to the custom calling convention) or by marking, so we request a different temp. locations->AddTemp(Location::RequiresRegister()); RegisterSet caller_saves = RegisterSet::Empty(); InvokeRuntimeCallingConventionARMVIXL calling_convention; @@ -6059,7 +6106,9 @@ void InstructionCodeGeneratorARMVIXL::VisitLoadString(HLoadString* load) NO_THRE } case HLoadString::LoadKind::kBssEntry: { DCHECK(!codegen_->GetCompilerOptions().IsBootImage()); - vixl32::Register temp = RegisterFrom(locations->GetTemp(0)); + vixl32::Register temp = (!kUseReadBarrier || kUseBakerReadBarrier) + ? RegisterFrom(locations->GetTemp(0)) + : out; CodeGeneratorARMVIXL::PcRelativePatchInfo* labels = codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex()); codegen_->EmitMovwMovtPlaceholder(labels, temp); diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index 137b55423b..09612c8dbf 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -6057,6 +6057,7 @@ void LocationsBuilderX86::VisitLoadClass(HLoadClass* cls) { cls, Location::RegisterLocation(calling_convention.GetRegisterAt(0)), Location::RegisterLocation(EAX)); + DCHECK_EQ(calling_convention.GetRegisterAt(0), EAX); return; } DCHECK(!cls->NeedsAccessCheck()); @@ -6076,6 +6077,17 @@ void LocationsBuilderX86::VisitLoadClass(HLoadClass* cls) { locations->SetInAt(0, Location::RequiresRegister()); } locations->SetOut(Location::RequiresRegister()); + if (load_kind == HLoadClass::LoadKind::kBssEntry) { + if (!kUseReadBarrier || kUseBakerReadBarrier) { + // Rely on the type resolution and/or initialization to save everything. + RegisterSet caller_saves = RegisterSet::Empty(); + InvokeRuntimeCallingConvention calling_convention; + caller_saves.Add(Location::RegisterLocation(calling_convention.GetRegisterAt(0))); + locations->SetCustomSlowPathCallerSaves(caller_saves); + } else { + // For non-Baker read barrier we have a temp-clobbering call. + } + } } Label* CodeGeneratorX86::NewJitRootClassPatch(const DexFile& dex_file, @@ -6158,7 +6170,7 @@ void InstructionCodeGeneratorX86::VisitLoadClass(HLoadClass* cls) NO_THREAD_SAFE Label* fixup_label = codegen_->NewJitRootClassPatch( cls->GetDexFile(), cls->GetTypeIndex(), cls->GetClass()); // /* GcRoot<mirror::Class> */ out = *address - GenerateGcRootFieldLoad(cls, out_loc, address, fixup_label, kCompilerReadBarrierOption); + GenerateGcRootFieldLoad(cls, out_loc, address, fixup_label, read_barrier_option); break; } case HLoadClass::LoadKind::kDexCacheViaMethod: @@ -6250,7 +6262,7 @@ void LocationsBuilderX86::VisitLoadString(HLoadString* load) { locations->SetOut(Location::RequiresRegister()); if (load_kind == HLoadString::LoadKind::kBssEntry) { if (!kUseReadBarrier || kUseBakerReadBarrier) { - // Rely on the pResolveString and/or marking to save everything. + // Rely on the pResolveString to save everything. RegisterSet caller_saves = RegisterSet::Empty(); InvokeRuntimeCallingConvention calling_convention; caller_saves.Add(Location::RegisterLocation(calling_convention.GetRegisterAt(0))); @@ -7136,9 +7148,10 @@ void InstructionCodeGeneratorX86::GenerateGcRootFieldLoad( // Fast path implementation of art::ReadBarrier::BarrierForRoot when // Baker's read barrier are used: // - // root = *address; - // if (Thread::Current()->GetIsGcMarking()) { - // root = ReadBarrier::Mark(root) + // root = obj.field; + // temp = Thread::Current()->pReadBarrierMarkReg ## root.reg() + // if (temp != null) { + // root = temp(root) // } // /* GcRoot<mirror::Object> */ root = *address @@ -7159,8 +7172,11 @@ void InstructionCodeGeneratorX86::GenerateGcRootFieldLoad( instruction, root, /* unpoison_ref_before_marking */ false); codegen_->AddSlowPath(slow_path); - __ fs()->cmpl(Address::Absolute(Thread::IsGcMarkingOffset<kX86PointerSize>().Int32Value()), - Immediate(0)); + // Test the entrypoint (`Thread::Current()->pReadBarrierMarkReg ## root.reg()`). + const int32_t entry_point_offset = + CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kX86PointerSize>(root.reg()); + __ fs()->cmpl(Address::Absolute(entry_point_offset), Immediate(0)); + // The entrypoint is null when the GC is not marking. __ j(kNotEqual, slow_path->GetEntryLabel()); __ Bind(slow_path->GetExitLabel()); } else { diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index c5367ce86e..0879992e32 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -245,9 +245,8 @@ class LoadClassSlowPathX86_64 : public SlowPathCode { SaveLiveRegisters(codegen, locations); - InvokeRuntimeCallingConvention calling_convention; - __ movl(CpuRegister(calling_convention.GetRegisterAt(0)), - Immediate(cls_->GetTypeIndex().index_)); + // Custom calling convention: RAX serves as both input and output. + __ movl(CpuRegister(RAX), Immediate(cls_->GetTypeIndex().index_)); x86_64_codegen->InvokeRuntime(do_clinit_ ? kQuickInitializeStaticStorage : kQuickInitializeType, instruction_, dex_pc_, @@ -5456,10 +5455,10 @@ HLoadClass::LoadKind CodeGeneratorX86_64::GetSupportedLoadClassKind( void LocationsBuilderX86_64::VisitLoadClass(HLoadClass* cls) { HLoadClass::LoadKind load_kind = cls->GetLoadKind(); if (load_kind == HLoadClass::LoadKind::kDexCacheViaMethod) { - InvokeRuntimeCallingConvention calling_convention; + // Custom calling convention: RAX serves as both input and output. CodeGenerator::CreateLoadClassRuntimeCallLocationSummary( cls, - Location::RegisterLocation(calling_convention.GetRegisterAt(0)), + Location::RegisterLocation(RAX), Location::RegisterLocation(RAX)); return; } @@ -5478,6 +5477,17 @@ void LocationsBuilderX86_64::VisitLoadClass(HLoadClass* cls) { locations->SetInAt(0, Location::RequiresRegister()); } locations->SetOut(Location::RequiresRegister()); + if (load_kind == HLoadClass::LoadKind::kBssEntry) { + if (!kUseReadBarrier || kUseBakerReadBarrier) { + // Rely on the type resolution and/or initialization to save everything. + // Custom calling convention: RAX serves as both input and output. + RegisterSet caller_saves = RegisterSet::Empty(); + caller_saves.Add(Location::RegisterLocation(RAX)); + locations->SetCustomSlowPathCallerSaves(caller_saves); + } else { + // For non-Baker read barrier we have a temp-clobbering call. + } + } } Label* CodeGeneratorX86_64::NewJitRootClassPatch(const DexFile& dex_file, @@ -5553,7 +5563,7 @@ void InstructionCodeGeneratorX86_64::VisitLoadClass(HLoadClass* cls) NO_THREAD_S Label* fixup_label = codegen_->NewJitRootClassPatch(cls->GetDexFile(), cls->GetTypeIndex(), cls->GetClass()); // /* GcRoot<mirror::Class> */ out = *address - GenerateGcRootFieldLoad(cls, out_loc, address, fixup_label, kCompilerReadBarrierOption); + GenerateGcRootFieldLoad(cls, out_loc, address, fixup_label, read_barrier_option); break; } default: @@ -5629,7 +5639,7 @@ void LocationsBuilderX86_64::VisitLoadString(HLoadString* load) { locations->SetOut(Location::RequiresRegister()); if (load->GetLoadKind() == HLoadString::LoadKind::kBssEntry) { if (!kUseReadBarrier || kUseBakerReadBarrier) { - // Rely on the pResolveString and/or marking to save everything. + // Rely on the pResolveString to save everything. // Custom calling convention: RAX serves as both input and output. RegisterSet caller_saves = RegisterSet::Empty(); caller_saves.Add(Location::RegisterLocation(RAX)); @@ -6501,9 +6511,10 @@ void InstructionCodeGeneratorX86_64::GenerateGcRootFieldLoad( // Fast path implementation of art::ReadBarrier::BarrierForRoot when // Baker's read barrier are used: // - // root = *address; - // if (Thread::Current()->GetIsGcMarking()) { - // root = ReadBarrier::Mark(root) + // root = obj.field; + // temp = Thread::Current()->pReadBarrierMarkReg ## root.reg() + // if (temp != null) { + // root = temp(root) // } // /* GcRoot<mirror::Object> */ root = *address @@ -6524,9 +6535,11 @@ void InstructionCodeGeneratorX86_64::GenerateGcRootFieldLoad( instruction, root, /* unpoison_ref_before_marking */ false); codegen_->AddSlowPath(slow_path); - __ gs()->cmpl(Address::Absolute(Thread::IsGcMarkingOffset<kX86_64PointerSize>().Int32Value(), - /* no_rip */ true), - Immediate(0)); + // Test the `Thread::Current()->pReadBarrierMarkReg ## root.reg()` entrypoint. + const int32_t entry_point_offset = + CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kX86_64PointerSize>(root.reg()); + __ gs()->cmpl(Address::Absolute(entry_point_offset, /* no_rip */ true), Immediate(0)); + // The entrypoint is null when the GC is not marking. __ j(kNotEqual, slow_path->GetEntryLabel()); __ Bind(slow_path->GetExitLabel()); } else { diff --git a/profman/profman.cc b/profman/profman.cc index ffebb6a2ea..b0cbed1ef9 100644 --- a/profman/profman.cc +++ b/profman/profman.cc @@ -248,8 +248,11 @@ class ProfMan FINAL { return result; } - int DumpOneProfile(const std::string& banner, const std::string& filename, int fd, - const std::vector<const DexFile*>* dex_files, std::string* dump) { + int DumpOneProfile(const std::string& banner, + const std::string& filename, + int fd, + const std::vector<std::unique_ptr<const DexFile>>* dex_files, + std::string* dump) { if (!filename.empty()) { fd = open(filename.c_str(), O_RDWR); if (fd < 0) { @@ -277,7 +280,7 @@ class ProfMan FINAL { // Open apk/zip files and and read dex files. MemMap::Init(); // for ZipArchive::OpenFromFd - std::vector<const DexFile*> dex_files; + std::vector<std::unique_ptr<const DexFile>> dex_files; assert(dex_locations_.size() == apks_fd_.size()); static constexpr bool kVerifyChecksum = true; for (size_t i = 0; i < dex_locations_.size(); ++i) { @@ -293,7 +296,7 @@ class ProfMan FINAL { continue; } for (std::unique_ptr<const DexFile>& dex_file : dex_files_for_location) { - dex_files.push_back(dex_file.release()); + dex_files.emplace_back(std::move(dex_file)); } } diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S index a443a4060d..cfe8406fbf 100644 --- a/runtime/arch/arm/quick_entrypoints_arm.S +++ b/runtime/arch/arm/quick_entrypoints_arm.S @@ -965,9 +965,27 @@ ENTRY \name END \name .endm -ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -ONE_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -ONE_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +// Macro for string and type resolution and initialization. +.macro ONE_ARG_SAVE_EVERYTHING_DOWNCALL name, entrypoint + .extern \entrypoint +ENTRY \name + SETUP_SAVE_EVERYTHING_FRAME r1 @ save everything in case of GC + mov r1, r9 @ pass Thread::Current + bl \entrypoint @ (uint32_t index, Thread*) + cbz r0, 1f @ If result is null, deliver the OOME. + .cfi_remember_state + RESTORE_SAVE_EVERYTHING_FRAME_KEEP_R0 + bx lr + .cfi_restore_state +1: + DELIVER_PENDING_EXCEPTION_FRAME_READY +END \name +.endm + +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_resolve_string, artResolveStringFromCode /* * Called by managed code to resolve a static field and load a non-wide value. @@ -1066,27 +1084,6 @@ ENTRY art_quick_set64_static DELIVER_PENDING_EXCEPTION END art_quick_set64_static - /* - * Entry from managed code to resolve a string, this stub will - * check the dex cache for a matching string (the fast path), and if not found, - * it will allocate a String and deliver an exception on error. - * On success the String is returned. R0 holds the string index. - */ - -ENTRY art_quick_resolve_string - SETUP_SAVE_EVERYTHING_FRAME r1 @ save everything in case of GC - mov r1, r9 @ pass Thread::Current - bl artResolveStringFromCode @ (uint32_t type_idx, Thread*) - cbz r0, 1f @ If result is null, deliver the OOME. - .cfi_remember_state - RESTORE_SAVE_EVERYTHING_FRAME_KEEP_R0 - bx lr - .cfi_restore_state -1: - DELIVER_PENDING_EXCEPTION_FRAME_READY -END art_quick_resolve_string - - // Generate the allocation entrypoints for each allocator. GENERATE_ALLOC_ENTRYPOINTS_FOR_NON_TLAB_ALLOCATORS // Comment out allocators that have arm specific asm. @@ -2057,7 +2054,9 @@ ENTRY \name beq .Lret_forwarding_address\name .Lslow_rb_\name: - // Save IP: the kSaveEverything entrypoint art_quick_resolve_string makes a tail call here. + // Save IP: The kSaveEverything entrypoint art_quick_resolve_string used to + // make a tail call here. Currently, it serves only for stack alignment but + // we may reintroduce kSaveEverything calls here in the future. push {r0-r4, r9, ip, lr} @ save return address, core caller-save registers and ip .cfi_adjust_cfa_offset 32 .cfi_rel_offset r0, 0 diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S index 219d8b447a..bfbe4816ba 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -1553,6 +1553,24 @@ ENTRY \name END \name .endm +// Macro for string and type resolution and initialization. +.macro ONE_ARG_SAVE_EVERYTHING_DOWNCALL name, entrypoint + .extern \entrypoint +ENTRY \name + SETUP_SAVE_EVERYTHING_FRAME // save everything for stack crawl + mov x1, xSELF // pass Thread::Current + bl \entrypoint // (int32_t index, Thread* self) + cbz w0, 1f // If result is null, deliver the OOME. + .cfi_remember_state + RESTORE_SAVE_EVERYTHING_FRAME_KEEP_X0 + ret // return + .cfi_restore_state + .cfi_def_cfa_offset FRAME_SIZE_SAVE_EVERYTHING // workaround for clang bug: 31975598 +1: + DELIVER_PENDING_EXCEPTION_FRAME_READY +END \name +.endm + .macro RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER cbz w0, 1f // result zero branch over ret // return @@ -1571,10 +1589,11 @@ TWO_ARG_REF_DOWNCALL art_quick_handle_fill_data, artHandleFillArrayDataFromCode, * initializer and deliver the exception on error. On success the static storage base is * returned. */ -ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode -ONE_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -ONE_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_resolve_string, artResolveStringFromCode ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 @@ -1604,27 +1623,6 @@ THREE_ARG_REF_DOWNCALL art_quick_set32_instance, artSet32InstanceFromCompiledCod THREE_ARG_REF_DOWNCALL art_quick_set64_instance, artSet64InstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER THREE_ARG_REF_DOWNCALL art_quick_set_obj_instance, artSetObjInstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER - /* - * Entry from managed code to resolve a string, this stub will - * check the dex cache for a matching string (the fast path), and if not found, - * it will allocate a String and deliver an exception on error. - * On success the String is returned. R0 holds the string index. - */ - -ENTRY art_quick_resolve_string - SETUP_SAVE_EVERYTHING_FRAME // save everything for stack crawl - mov x1, xSELF // pass Thread::Current - bl artResolveStringFromCode // (int32_t string_idx, Thread* self) - cbz w0, 1f // If result is null, deliver the OOME. - .cfi_remember_state - RESTORE_SAVE_EVERYTHING_FRAME_KEEP_X0 - ret // return - .cfi_restore_state - .cfi_def_cfa_offset FRAME_SIZE_SAVE_EVERYTHING // workaround for clang bug: 31975598 -1: - DELIVER_PENDING_EXCEPTION_FRAME_READY -END art_quick_resolve_string - // Generate the allocation entrypoints for each allocator. GENERATE_ALLOC_ENTRYPOINTS_FOR_NON_TLAB_ALLOCATORS // Comment out allocators that have arm64 specific asm. @@ -2380,13 +2378,6 @@ END art_quick_indexof ENTRY \name // Reference is null, no work to do at all. cbz \wreg, .Lret_rb_\name - /* - * Allocate 46 stack slots * 8 = 368 bytes: - * - 20 slots for core registers X0-X19 - * - 24 slots for floating-point registers D0-D7 and D16-D31 - * - 1 slot for return address register XLR - * - 1 padding slot for 16-byte stack alignment - */ // Use wIP0 as temp and check the mark bit of the reference. wIP0 is not used by the compiler. ldr wIP0, [\xreg, #MIRROR_OBJECT_LOCK_WORD_OFFSET] tbz wIP0, #LOCK_WORD_MARK_BIT_SHIFT, .Lnot_marked_rb_\name @@ -2398,10 +2389,15 @@ ENTRY \name cmp wzr, wIP0, lsr #30 beq .Lret_forwarding_address\name .Lslow_rb_\name: - // We must not clobber IP0 since art_quick_resolve_string makes a tail call here and relies on - // IP0 being restored. + /* + * Allocate 44 stack slots * 8 = 352 bytes: + * - 20 slots for core registers X0-15, X17-X19, LR + * - 24 slots for floating-point registers D0-D7 and D16-D31 + */ + // We must not clobber IP1 since code emitted for HLoadClass and HLoadString + // relies on IP1 being preserved. // Save all potentially live caller-save core registers. - SAVE_TWO_REGS_INCREASE_FRAME x0, x1, 368 + SAVE_TWO_REGS_INCREASE_FRAME x0, x1, 352 SAVE_TWO_REGS x2, x3, 16 SAVE_TWO_REGS x4, x5, 32 SAVE_TWO_REGS x6, x7, 48 @@ -2409,8 +2405,8 @@ ENTRY \name SAVE_TWO_REGS x10, x11, 80 SAVE_TWO_REGS x12, x13, 96 SAVE_TWO_REGS x14, x15, 112 - SAVE_TWO_REGS x16, x17, 128 - SAVE_TWO_REGS x18, x19, 144 + SAVE_TWO_REGS x17, x18, 128 // Skip x16, i.e. IP0. + SAVE_TWO_REGS x19, xLR, 144 // Save also return address. // Save all potentially live caller-save floating-point registers. stp d0, d1, [sp, #160] stp d2, d3, [sp, #176] @@ -2424,9 +2420,6 @@ ENTRY \name stp d26, d27, [sp, #304] stp d28, d29, [sp, #320] stp d30, d31, [sp, #336] - // Save return address. - // (sp + #352 is a padding slot) - SAVE_REG xLR, 360 .ifnc \wreg, w0 mov w0, \wreg // Pass arg1 - obj from `wreg` @@ -2446,8 +2439,8 @@ ENTRY \name POP_REGS_NE x10, x11, 80, \xreg POP_REGS_NE x12, x13, 96, \xreg POP_REGS_NE x14, x15, 112, \xreg - POP_REGS_NE x16, x17, 128, \xreg - POP_REGS_NE x18, x19, 144, \xreg + POP_REGS_NE x17, x18, 128, \xreg + POP_REGS_NE x19, xLR, 144, \xreg // Restore also return address. // Restore floating-point registers. ldp d0, d1, [sp, #160] ldp d2, d3, [sp, #176] @@ -2461,9 +2454,8 @@ ENTRY \name ldp d26, d27, [sp, #304] ldp d28, d29, [sp, #320] ldp d30, d31, [sp, #336] - // Restore return address and remove padding. - RESTORE_REG xLR, 360 - DECREASE_FRAME 368 + // Remove frame and return. + DECREASE_FRAME 352 ret .Lret_forwarding_address\name: mvn wIP0, wIP0 diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S index 663cb6c62f..10b690ac4a 100644 --- a/runtime/arch/mips/quick_entrypoints_mips.S +++ b/runtime/arch/mips/quick_entrypoints_mips.S @@ -1576,9 +1576,87 @@ END \name // Generate the allocation entrypoints for each allocator. GENERATE_ALLOC_ENTRYPOINTS_FOR_EACH_ALLOCATOR +// A hand-written override for: +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_rosalloc, RosAlloc) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_rosalloc, RosAlloc) +.macro ART_QUICK_ALLOC_OBJECT_ROSALLOC c_name, cxx_name +ENTRY \c_name + # Fast path rosalloc allocation + # a0: type + # s1: Thread::Current + # ----------------------------- + # t1: object size + # t2: rosalloc run + # t3: thread stack top offset + # t4: thread stack bottom offset + # v0: free list head + # + # t5, t6 : temps + lw $t3, THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET($s1) # Check if thread local allocation + lw $t4, THREAD_LOCAL_ALLOC_STACK_END_OFFSET($s1) # stack has any room left. + bgeu $t3, $t4, .Lslow_path_\c_name + + lw $t1, MIRROR_CLASS_OBJECT_SIZE_ALLOC_FAST_PATH_OFFSET($a0) # Load object size (t1). + li $t5, ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE # Check if size is for a thread local + # allocation. Also does the + # initialized and finalizable checks. + bgtu $t1, $t5, .Lslow_path_\c_name + + # Compute the rosalloc bracket index from the size. Since the size is already aligned we can + # combine the two shifts together. + srl $t1, $t1, (ROSALLOC_BRACKET_QUANTUM_SIZE_SHIFT - POINTER_SIZE_SHIFT) + + addu $t2, $t1, $s1 + lw $t2, (THREAD_ROSALLOC_RUNS_OFFSET - __SIZEOF_POINTER__)($t2) # Load rosalloc run (t2). + + # Load the free list head (v0). + # NOTE: this will be the return val. + lw $v0, (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_HEAD_OFFSET)($t2) + beqz $v0, .Lslow_path_\c_name + nop + + # Load the next pointer of the head and update the list head with the next pointer. + lw $t5, ROSALLOC_SLOT_NEXT_OFFSET($v0) + sw $t5, (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_HEAD_OFFSET)($t2) + + # Store the class pointer in the header. This also overwrites the first pointer. The offsets are + # asserted to match. + +#if ROSALLOC_SLOT_NEXT_OFFSET != MIRROR_OBJECT_CLASS_OFFSET +#error "Class pointer needs to overwrite next pointer." +#endif + + POISON_HEAP_REF $a0 + sw $a0, MIRROR_OBJECT_CLASS_OFFSET($v0) + + # Push the new object onto the thread local allocation stack and increment the thread local + # allocation stack top. + sw $v0, 0($t3) + addiu $t3, $t3, COMPRESSED_REFERENCE_SIZE + sw $t3, THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET($s1) + + # Decrement the size of the free list. + lw $t5, (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_SIZE_OFFSET)($t2) + addiu $t5, $t5, -1 + sw $t5, (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_SIZE_OFFSET)($t2) + + sync # Fence. + + jalr $zero, $ra + nop + + .Lslow_path_\c_name: + SETUP_SAVE_REFS_ONLY_FRAME + la $t9, \cxx_name + jalr $t9 + move $a1, $s1 # Pass self as argument. + RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +END \c_name +.endm + +ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_resolved_rosalloc, artAllocObjectFromCodeResolvedRosAlloc +ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_initialized_rosalloc, artAllocObjectFromCodeInitializedRosAlloc -GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_rosalloc, RosAlloc) -GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_rosalloc, RosAlloc) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_tlab, TLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region_tlab, RegionTLAB) diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S index 5fee575331..fff6d256b1 100644 --- a/runtime/arch/mips64/quick_entrypoints_mips64.S +++ b/runtime/arch/mips64/quick_entrypoints_mips64.S @@ -1533,8 +1533,85 @@ END \name // Generate the allocation entrypoints for each allocator. GENERATE_ALLOC_ENTRYPOINTS_FOR_EACH_ALLOCATOR -GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_rosalloc, RosAlloc) -GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_rosalloc, RosAlloc) +// A hand-written override for: +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_rosalloc, RosAlloc) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_rosalloc, RosAlloc) +.macro ART_QUICK_ALLOC_OBJECT_ROSALLOC c_name, cxx_name +ENTRY \c_name + # Fast path rosalloc allocation + # a0: type + # s1: Thread::Current + # ----------------------------- + # t1: object size + # t2: rosalloc run + # t3: thread stack top offset + # a4: thread stack bottom offset + # v0: free list head + # + # a5, a6 : temps + ld $t3, THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET($s1) # Check if thread local allocation stack + ld $a4, THREAD_LOCAL_ALLOC_STACK_END_OFFSET($s1) # has any room left. + bgeuc $t3, $a4, .Lslow_path_\c_name + + lwu $t1, MIRROR_CLASS_OBJECT_SIZE_ALLOC_FAST_PATH_OFFSET($a0) # Load object size (t1). + li $a5, ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE # Check if size is for a thread local + # allocation. Also does the initialized + # and finalizable checks. + bltuc $a5, $t1, .Lslow_path_\c_name + + # Compute the rosalloc bracket index from the size. Since the size is already aligned we can + # combine the two shifts together. + dsrl $t1, $t1, (ROSALLOC_BRACKET_QUANTUM_SIZE_SHIFT - POINTER_SIZE_SHIFT) + + daddu $t2, $t1, $s1 + ld $t2, (THREAD_ROSALLOC_RUNS_OFFSET - __SIZEOF_POINTER__)($t2) # Load rosalloc run (t2). + + # Load the free list head (v0). + # NOTE: this will be the return val. + ld $v0, (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_HEAD_OFFSET)($t2) + beqzc $v0, .Lslow_path_\c_name + + # Load the next pointer of the head and update the list head with the next pointer. + ld $a5, ROSALLOC_SLOT_NEXT_OFFSET($v0) + sd $a5, (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_HEAD_OFFSET)($t2) + + # Store the class pointer in the header. This also overwrites the first pointer. The offsets are + # asserted to match. + +#if ROSALLOC_SLOT_NEXT_OFFSET != MIRROR_OBJECT_CLASS_OFFSET +#error "Class pointer needs to overwrite next pointer." +#endif + + POISON_HEAP_REF $a0 + sw $a0, MIRROR_OBJECT_CLASS_OFFSET($v0) + + # Push the new object onto the thread local allocation stack and increment the thread local + # allocation stack top. + sd $v0, 0($t3) + daddiu $t3, $t3, COMPRESSED_REFERENCE_SIZE + sd $t3, THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET($s1) + + # Decrement the size of the free list. + lw $a5, (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_SIZE_OFFSET)($t2) + addiu $a5, $a5, -1 + sw $a5, (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_SIZE_OFFSET)($t2) + + sync # Fence. + + jalr $zero, $ra + .cpreturn # Restore gp from t8 in branch delay slot. + +.Lslow_path_\c_name: + SETUP_SAVE_REFS_ONLY_FRAME + jal \cxx_name + move $a1 ,$s1 # Pass self as argument. + RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +END \c_name +.endm + +ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_resolved_rosalloc, artAllocObjectFromCodeResolvedRosAlloc +ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_initialized_rosalloc, artAllocObjectFromCodeInitializedRosAlloc + GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_tlab, TLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region_tlab, RegionTLAB) diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S index 76615e843b..8c907e0790 100644 --- a/runtime/arch/x86/quick_entrypoints_x86.S +++ b/runtime/arch/x86/quick_entrypoints_x86.S @@ -922,6 +922,31 @@ MACRO3(THREE_ARG_REF_DOWNCALL, c_name, cxx_name, return_macro) END_FUNCTION VAR(c_name) END_MACRO +// Macro for string and type resolution and initialization. +MACRO2(ONE_ARG_SAVE_EVERYTHING_DOWNCALL, c_name, cxx_name) + DEFINE_FUNCTION VAR(c_name) + SETUP_SAVE_EVERYTHING_FRAME ebx, ebx // save ref containing registers for GC + // Outgoing argument set up + subl MACRO_LITERAL(8), %esp // push padding + CFI_ADJUST_CFA_OFFSET(8) + pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current() + CFI_ADJUST_CFA_OFFSET(4) + PUSH eax // pass arg1 + call CALLVAR(cxx_name) // cxx_name(arg1, Thread*) + addl MACRO_LITERAL(16), %esp // pop arguments + CFI_ADJUST_CFA_OFFSET(-16) + testl %eax, %eax // If result is null, deliver the OOME. + jz 1f + CFI_REMEMBER_STATE + RESTORE_SAVE_EVERYTHING_FRAME_KEEP_EAX // restore frame up to return address + ret // return + CFI_RESTORE_STATE + CFI_DEF_CFA(esp, FRAME_SIZE_SAVE_EVERYTHING) // workaround for clang bug: 31975598 +1: + DELIVER_PENDING_EXCEPTION_FRAME_READY + END_FUNCTION VAR(c_name) +END_MACRO + MACRO0(RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER) testl %eax, %eax // eax == 0 ? jz 1f // if eax == 0 goto 1 @@ -1245,31 +1270,10 @@ GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved16_tlab, artAllocArrayFr GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved32_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_32 GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved64_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_64 -DEFINE_FUNCTION art_quick_resolve_string - SETUP_SAVE_EVERYTHING_FRAME ebx, ebx - // Outgoing argument set up - subl LITERAL(8), %esp // push padding - CFI_ADJUST_CFA_OFFSET(8) - pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current() - CFI_ADJUST_CFA_OFFSET(4) - PUSH eax // pass arg1 - call SYMBOL(artResolveStringFromCode) - addl LITERAL(16), %esp // pop arguments - CFI_ADJUST_CFA_OFFSET(-16) - testl %eax, %eax // If result is null, deliver the OOME. - jz 1f - CFI_REMEMBER_STATE - RESTORE_SAVE_EVERYTHING_FRAME_KEEP_EAX - ret - CFI_RESTORE_STATE - CFI_DEF_CFA(esp, FRAME_SIZE_SAVE_EVERYTHING) // workaround for clang bug: 31975598 -1: - DELIVER_PENDING_EXCEPTION_FRAME_READY -END_FUNCTION art_quick_resolve_string - -ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -ONE_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -ONE_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_resolve_string, artResolveStringFromCode TWO_ARG_REF_DOWNCALL art_quick_handle_fill_data, artHandleFillArrayDataFromCode, RETURN_IF_EAX_ZERO diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S index a1ae858735..f1be52eeb6 100644 --- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S +++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S @@ -950,6 +950,26 @@ MACRO3(THREE_ARG_REF_DOWNCALL, c_name, cxx_name, return_macro) END_FUNCTION VAR(c_name) END_MACRO +// Macro for string and type resolution and initialization. +MACRO2(ONE_ARG_SAVE_EVERYTHING_DOWNCALL, c_name, cxx_name) + DEFINE_FUNCTION VAR(c_name) + SETUP_SAVE_EVERYTHING_FRAME // save everything for GC + // Outgoing argument set up + movl %eax, %edi // pass string index + movq %gs:THREAD_SELF_OFFSET, %rsi // pass Thread::Current() + call CALLVAR(cxx_name) // cxx_name(arg0, Thread*) + testl %eax, %eax // If result is null, deliver the OOME. + jz 1f + CFI_REMEMBER_STATE + RESTORE_SAVE_EVERYTHING_FRAME_KEEP_RAX // restore frame up to return address + ret + CFI_RESTORE_STATE + CFI_DEF_CFA(rsp, FRAME_SIZE_SAVE_EVERYTHING) // workaround for clang bug: 31975598 +1: + DELIVER_PENDING_EXCEPTION_FRAME_READY + END_FUNCTION VAR(c_name) +END_MACRO + MACRO0(RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER) testq %rax, %rax // rax == 0 ? jz 1f // if rax == 0 goto 1 @@ -1270,27 +1290,10 @@ DEFINE_FUNCTION art_quick_alloc_object_initialized_region_tlab ALLOC_OBJECT_TLAB_SLOW_PATH artAllocObjectFromCodeInitializedRegionTLAB END_FUNCTION art_quick_alloc_object_initialized_region_tlab -DEFINE_FUNCTION art_quick_resolve_string - SETUP_SAVE_EVERYTHING_FRAME - // Outgoing argument set up - movl %eax, %edi // pass string index - movq %gs:THREAD_SELF_OFFSET, %rsi // pass Thread::Current() - call SYMBOL(artResolveStringFromCode) // artResolveStringFromCode(arg0, Thread*) - - testl %eax, %eax // If result is null, deliver the OOME. - jz 1f - CFI_REMEMBER_STATE - RESTORE_SAVE_EVERYTHING_FRAME_KEEP_RAX // restore frame up to return address - ret - CFI_RESTORE_STATE - CFI_DEF_CFA(rsp, FRAME_SIZE_SAVE_EVERYTHING) // workaround for clang bug: 31975598 -1: - DELIVER_PENDING_EXCEPTION_FRAME_READY -END_FUNCTION art_quick_resolve_string - -ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -ONE_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -ONE_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode +ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_resolve_string, artResolveStringFromCode TWO_ARG_REF_DOWNCALL art_quick_handle_fill_data, artHandleFillArrayDataFromCode, RETURN_IF_EAX_ZERO diff --git a/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc b/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc index 5b1b2871c2..699cf91c70 100644 --- a/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc @@ -53,13 +53,18 @@ static inline void BssWriteBarrier(ArtMethod* outer_method) REQUIRES_SHARED(Lock } } +constexpr Runtime::CalleeSaveType kInitEntrypointSaveType = + // TODO: Change allocation entrypoints on MIPS and MIPS64 to kSaveEverything. + (kRuntimeISA == kMips || kRuntimeISA == kMips64) ? Runtime::kSaveRefsOnly + : Runtime::kSaveEverything; + extern "C" mirror::Class* artInitializeStaticStorageFromCode(uint32_t type_idx, Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { // Called to ensure static storage base is initialized for direct static field reads and writes. // A class may be accessing another class' fields when it doesn't have access, as access has been // given by inheritance. ScopedQuickEntrypointChecks sqec(self); - auto caller_and_outer = GetCalleeSaveMethodCallerAndOuterMethod(self, Runtime::kSaveRefsOnly); + auto caller_and_outer = GetCalleeSaveMethodCallerAndOuterMethod(self, kInitEntrypointSaveType); ArtMethod* caller = caller_and_outer.caller; mirror::Class* result = ResolveVerifyAndClinit(dex::TypeIndex(type_idx), caller, self, true, false); @@ -73,7 +78,7 @@ extern "C" mirror::Class* artInitializeTypeFromCode(uint32_t type_idx, Thread* s REQUIRES_SHARED(Locks::mutator_lock_) { // Called when method->dex_cache_resolved_types_[] misses. ScopedQuickEntrypointChecks sqec(self); - auto caller_and_outer = GetCalleeSaveMethodCallerAndOuterMethod(self, Runtime::kSaveRefsOnly); + auto caller_and_outer = GetCalleeSaveMethodCallerAndOuterMethod(self, kInitEntrypointSaveType); ArtMethod* caller = caller_and_outer.caller; mirror::Class* result = ResolveVerifyAndClinit(dex::TypeIndex(type_idx), caller, self, false, false); @@ -88,7 +93,7 @@ extern "C" mirror::Class* artInitializeTypeAndVerifyAccessFromCode(uint32_t type // Called when caller isn't guaranteed to have access to a type and the dex cache may be // unpopulated. ScopedQuickEntrypointChecks sqec(self); - auto caller_and_outer = GetCalleeSaveMethodCallerAndOuterMethod(self, Runtime::kSaveRefsOnly); + auto caller_and_outer = GetCalleeSaveMethodCallerAndOuterMethod(self, kInitEntrypointSaveType); ArtMethod* caller = caller_and_outer.caller; mirror::Class* result = ResolveVerifyAndClinit(dex::TypeIndex(type_idx), caller, self, false, true); @@ -101,11 +106,7 @@ extern "C" mirror::Class* artInitializeTypeAndVerifyAccessFromCode(uint32_t type extern "C" mirror::String* artResolveStringFromCode(int32_t string_idx, Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { ScopedQuickEntrypointChecks sqec(self); - auto caller_and_outer = GetCalleeSaveMethodCallerAndOuterMethod( - self, - // TODO: Change art_quick_resolve_string on MIPS and MIPS64 to kSaveEverything. - (kRuntimeISA == kMips || kRuntimeISA == kMips64) ? Runtime::kSaveRefsOnly - : Runtime::kSaveEverything); + auto caller_and_outer = GetCalleeSaveMethodCallerAndOuterMethod(self, kInitEntrypointSaveType); ArtMethod* caller = caller_and_outer.caller; mirror::String* result = ResolveStringFromCode(caller, dex::StringIndex(string_idx)); if (LIKELY(result != nullptr)) { diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index 051f3f7b00..0a45fcedae 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -3887,13 +3887,15 @@ void Heap::RegisterNativeAllocation(JNIEnv* env, size_t bytes) { // blocking watermark. Ensure that only one of those threads runs the // blocking GC. The rest of the threads should instead wait for the // blocking GC to complete. - if (native_blocking_gc_in_progress_) { - do { - native_blocking_gc_cond_->Wait(self); - } while (native_blocking_gcs_finished_ == initial_gcs_finished); - } else { - native_blocking_gc_in_progress_ = true; - run_gc = true; + if (native_blocking_gcs_finished_ == initial_gcs_finished) { + if (native_blocking_gc_in_progress_) { + do { + native_blocking_gc_cond_->Wait(self); + } while (native_blocking_gcs_finished_ == initial_gcs_finished); + } else { + native_blocking_gc_in_progress_ = true; + run_gc = true; + } } } diff --git a/runtime/hprof/hprof.cc b/runtime/hprof/hprof.cc index 3d3ad593b3..133502e6a3 100644 --- a/runtime/hprof/hprof.cc +++ b/runtime/hprof/hprof.cc @@ -224,12 +224,6 @@ class EndianOutput { HandleU1List(values, count); length_ += count; } - void AddU1AsU2List(const uint8_t* values, size_t count) { - HandleU1AsU2List(values, count); - // Array of char from compressed String (8-bit) is added as 16-bit blocks - int ceil_count_to_even = count + ((count & 1) ? 1 : 0); - length_ += ceil_count_to_even * sizeof(uint8_t); - } void AddU2List(const uint16_t* values, size_t count) { HandleU2List(values, count); length_ += count * sizeof(uint16_t); @@ -1277,7 +1271,7 @@ void Hprof::DumpHeapClass(mirror::Class* klass) { HprofBasicType t = SignatureToBasicTypeAndSize(f->GetTypeDescriptor(), nullptr); __ AddU1(t); } - // Add native value character array for strings. + // Add native value character array for strings / byte array for compressed strings. if (klass->IsStringClass()) { __ AddStringId(LookupStringId("value")); __ AddU1(hprof_basic_object); @@ -1359,8 +1353,16 @@ void Hprof::DumpHeapInstanceObject(mirror::Object* obj, mirror::Class* klass) { case hprof_basic_short: __ AddU2(f->GetShort(obj)); break; - case hprof_basic_float: case hprof_basic_int: + if (mirror::kUseStringCompression && + klass->IsStringClass() && + f->GetOffset().SizeValue() == mirror::String::CountOffset().SizeValue()) { + // Store the string length instead of the raw count field with compression flag. + __ AddU4(obj->AsString()->GetLength()); + break; + } + FALLTHROUGH_INTENDED; + case hprof_basic_float: case hprof_basic_object: __ AddU4(f->Get32(obj)); break; @@ -1397,16 +1399,15 @@ void Hprof::DumpHeapInstanceObject(mirror::Object* obj, mirror::Class* klass) { CHECK_EQ(obj->IsString(), string_value != nullptr); if (string_value != nullptr) { mirror::String* s = obj->AsString(); - // Compressed string's (8-bit) length is ceil(length/2) in 16-bit blocks - int length_in_16_bit = (s->IsCompressed()) ? ((s->GetLength() + 1) / 2) : s->GetLength(); __ AddU1(HPROF_PRIMITIVE_ARRAY_DUMP); __ AddObjectId(string_value); __ AddStackTraceSerialNumber(LookupStackTraceSerialNumber(obj)); - __ AddU4(length_in_16_bit); - __ AddU1(hprof_basic_char); + __ AddU4(s->GetLength()); if (s->IsCompressed()) { - __ AddU1AsU2List(s->GetValueCompressed(), s->GetLength()); + __ AddU1(hprof_basic_byte); + __ AddU1List(s->GetValueCompressed(), s->GetLength()); } else { + __ AddU1(hprof_basic_char); __ AddU2List(s->GetValue(), s->GetLength()); } } diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc index 371e2f1e65..545cc1ad42 100644 --- a/runtime/interpreter/unstarted_runtime.cc +++ b/runtime/interpreter/unstarted_runtime.cc @@ -21,6 +21,7 @@ #include <stdlib.h> #include <cmath> +#include <initializer_list> #include <limits> #include <locale> #include <unordered_map> @@ -883,43 +884,74 @@ void UnstartedRuntime::UnstartedSystemGetPropertyWithDefault( GetSystemProperty(self, shadow_frame, result, arg_offset, true); } -void UnstartedRuntime::UnstartedThreadLocalGet( - Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset ATTRIBUTE_UNUSED) { - std::string caller(ArtMethod::PrettyMethod(shadow_frame->GetLink()->GetMethod())); - bool ok = false; - if (caller == "void java.lang.FloatingDecimal.developLongDigits(int, long, long)" || - caller == "java.lang.String java.lang.FloatingDecimal.toJavaFormatString()") { - // Allocate non-threadlocal buffer. - result->SetL(mirror::CharArray::Alloc(self, 26)); - ok = true; - } else if (caller == - "java.lang.FloatingDecimal java.lang.FloatingDecimal.getThreadLocalInstance()") { - // Allocate new object. - StackHandleScope<2> hs(self); - Handle<mirror::Class> h_real_to_string_class(hs.NewHandle( - shadow_frame->GetLink()->GetMethod()->GetDeclaringClass())); - Handle<mirror::Object> h_real_to_string_obj(hs.NewHandle( - h_real_to_string_class->AllocObject(self))); - if (h_real_to_string_obj.Get() != nullptr) { - auto* cl = Runtime::Current()->GetClassLinker(); - ArtMethod* init_method = h_real_to_string_class->FindDirectMethod( - "<init>", "()V", cl->GetImagePointerSize()); - if (init_method == nullptr) { - h_real_to_string_class->DumpClass(LOG_STREAM(FATAL), mirror::Class::kDumpClassFullDetail); - } else { - JValue invoke_result; - EnterInterpreterFromInvoke(self, init_method, h_real_to_string_obj.Get(), nullptr, - nullptr); - if (!self->IsExceptionPending()) { - result->SetL(h_real_to_string_obj.Get()); - ok = true; - } +static std::string GetImmediateCaller(ShadowFrame* shadow_frame) + REQUIRES_SHARED(Locks::mutator_lock_) { + if (shadow_frame->GetLink() == nullptr) { + return "<no caller>"; + } + return ArtMethod::PrettyMethod(shadow_frame->GetLink()->GetMethod()); +} + +static bool CheckCallers(ShadowFrame* shadow_frame, + std::initializer_list<std::string> allowed_call_stack) + REQUIRES_SHARED(Locks::mutator_lock_) { + for (const std::string& allowed_caller : allowed_call_stack) { + if (shadow_frame->GetLink() == nullptr) { + return false; + } + + std::string found_caller = ArtMethod::PrettyMethod(shadow_frame->GetLink()->GetMethod()); + if (allowed_caller != found_caller) { + return false; + } + + shadow_frame = shadow_frame->GetLink(); + } + return true; +} + +static ObjPtr<mirror::Object> CreateInstanceOf(Thread* self, const char* class_descriptor) + REQUIRES_SHARED(Locks::mutator_lock_) { + // Find the requested class. + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + ObjPtr<mirror::Class> klass = + class_linker->FindClass(self, class_descriptor, ScopedNullHandle<mirror::ClassLoader>()); + if (klass == nullptr) { + AbortTransactionOrFail(self, "Could not load class %s", class_descriptor); + return nullptr; + } + + StackHandleScope<2> hs(self); + Handle<mirror::Class> h_class(hs.NewHandle(klass)); + Handle<mirror::Object> h_obj(hs.NewHandle(h_class->AllocObject(self))); + if (h_obj.Get() != nullptr) { + ArtMethod* init_method = h_class->FindDirectMethod( + "<init>", "()V", class_linker->GetImagePointerSize()); + if (init_method == nullptr) { + AbortTransactionOrFail(self, "Could not find <init> for %s", class_descriptor); + return nullptr; + } else { + JValue invoke_result; + EnterInterpreterFromInvoke(self, init_method, h_obj.Get(), nullptr, nullptr); + if (!self->IsExceptionPending()) { + return h_obj.Get(); } + AbortTransactionOrFail(self, "Could not run <init> for %s", class_descriptor); } } + AbortTransactionOrFail(self, "Could not allocate instance of %s", class_descriptor); + return nullptr; +} - if (!ok) { - AbortTransactionOrFail(self, "Could not create RealToString object"); +void UnstartedRuntime::UnstartedThreadLocalGet( + Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset ATTRIBUTE_UNUSED) { + if (CheckCallers(shadow_frame, { "sun.misc.FloatingDecimal$BinaryToASCIIBuffer " + "sun.misc.FloatingDecimal.getBinaryToASCIIBuffer()" })) { + result->SetL(CreateInstanceOf(self, "Lsun/misc/FloatingDecimal$BinaryToASCIIBuffer;")); + } else { + AbortTransactionOrFail(self, + "ThreadLocal.get() does not support %s", + GetImmediateCaller(shadow_frame).c_str()); } } @@ -1252,12 +1284,12 @@ void UnstartedRuntime::UnstartedReferenceGetReferent( // initialization of other classes, so will *use* the value. void UnstartedRuntime::UnstartedRuntimeAvailableProcessors( Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset ATTRIBUTE_UNUSED) { - std::string caller(ArtMethod::PrettyMethod(shadow_frame->GetLink()->GetMethod())); - if (caller == "void java.util.concurrent.SynchronousQueue.<clinit>()") { + if (CheckCallers(shadow_frame, { "void java.util.concurrent.SynchronousQueue.<clinit>()" })) { // SynchronousQueue really only separates between single- and multiprocessor case. Return // 8 as a conservative upper approximation. result->SetI(8); - } else if (caller == "void java.util.concurrent.ConcurrentHashMap.<clinit>()") { + } else if (CheckCallers(shadow_frame, + { "void java.util.concurrent.ConcurrentHashMap.<clinit>()" })) { // ConcurrentHashMap uses it for striding. 8 still seems an OK general value, as it's likely // a good upper bound. // TODO: Consider resetting in the zygote? diff --git a/runtime/interpreter/unstarted_runtime_test.cc b/runtime/interpreter/unstarted_runtime_test.cc index ae55f4c2ef..31be587e9c 100644 --- a/runtime/interpreter/unstarted_runtime_test.cc +++ b/runtime/interpreter/unstarted_runtime_test.cc @@ -944,5 +944,100 @@ TEST_F(UnstartedRuntimeTest, GetDeclaringClass) { ShadowFrame::DeleteDeoptimizedFrame(shadow_frame); } +TEST_F(UnstartedRuntimeTest, ThreadLocalGet) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + JValue result; + ShadowFrame* shadow_frame = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0); + + StackHandleScope<1> hs(self); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + + // Positive test. See that We get something for float conversion. + { + Handle<mirror::Class> floating_decimal = hs.NewHandle( + class_linker->FindClass(self, + "Lsun/misc/FloatingDecimal;", + ScopedNullHandle<mirror::ClassLoader>())); + ASSERT_TRUE(floating_decimal.Get() != nullptr); + ASSERT_TRUE(class_linker->EnsureInitialized(self, floating_decimal, true, true)); + + ArtMethod* caller_method = floating_decimal->FindDeclaredDirectMethod( + "getBinaryToASCIIBuffer", + "()Lsun/misc/FloatingDecimal$BinaryToASCIIBuffer;", + class_linker->GetImagePointerSize()); + // floating_decimal->DumpClass(LOG_STREAM(ERROR), mirror::Class::kDumpClassFullDetail); + ASSERT_TRUE(caller_method != nullptr); + ShadowFrame* caller_frame = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, caller_method, 0); + shadow_frame->SetLink(caller_frame); + + UnstartedThreadLocalGet(self, shadow_frame, &result, 0); + EXPECT_TRUE(result.GetL() != nullptr); + EXPECT_FALSE(self->IsExceptionPending()); + + ShadowFrame::DeleteDeoptimizedFrame(caller_frame); + } + + // Negative test. + PrepareForAborts(); + + { + // Just use a method in Class. + ObjPtr<mirror::Class> class_class = mirror::Class::GetJavaLangClass(); + ArtMethod* caller_method = + &*class_class->GetDeclaredMethods(class_linker->GetImagePointerSize()).begin(); + ShadowFrame* caller_frame = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, caller_method, 0); + shadow_frame->SetLink(caller_frame); + + Transaction transaction; + Runtime::Current()->EnterTransactionMode(&transaction); + UnstartedThreadLocalGet(self, shadow_frame, &result, 0); + Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(self->IsExceptionPending()); + ASSERT_TRUE(transaction.IsAborted()); + self->ClearException(); + + ShadowFrame::DeleteDeoptimizedFrame(caller_frame); + } + + ShadowFrame::DeleteDeoptimizedFrame(shadow_frame); +} + +TEST_F(UnstartedRuntimeTest, FloatConversion) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + StackHandleScope<1> hs(self); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Handle<mirror::Class> double_class = hs.NewHandle( + class_linker->FindClass(self, + "Ljava/lang/Double;", + ScopedNullHandle<mirror::ClassLoader>())); + ASSERT_TRUE(double_class.Get() != nullptr); + ASSERT_TRUE(class_linker->EnsureInitialized(self, double_class, true, true)); + + ArtMethod* method = double_class->FindDeclaredDirectMethod("toString", + "(D)Ljava/lang/String;", + class_linker->GetImagePointerSize()); + ASSERT_TRUE(method != nullptr); + + // create instruction data for invoke-direct {v0, v1} of method with fake index + uint16_t inst_data[3] = { 0x2070, 0x0000, 0x0010 }; + const Instruction* inst = Instruction::At(inst_data); + + JValue result; + ShadowFrame* shadow_frame = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, method, 0); + shadow_frame->SetVRegDouble(0, 1.23); + interpreter::DoCall<false, false>(method, self, *shadow_frame, inst, inst_data[0], &result); + ObjPtr<mirror::String> string_result = reinterpret_cast<mirror::String*>(result.GetL()); + ASSERT_TRUE(string_result != nullptr); + + std::string mod_utf = string_result->ToModifiedUtf8(); + EXPECT_EQ("1.23", mod_utf); + + ShadowFrame::DeleteDeoptimizedFrame(shadow_frame); +} + } // namespace interpreter } // namespace art diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc index 1405c40096..9ba2d1a355 100644 --- a/runtime/jit/profile_compilation_info.cc +++ b/runtime/jit/profile_compilation_info.cc @@ -597,6 +597,24 @@ uint32_t ProfileCompilationInfo::GetNumberOfResolvedClasses() const { return total; } +// Produce a non-owning vector from a vector. +template<typename T> +const std::vector<T*>* MakeNonOwningVector(const std::vector<std::unique_ptr<T>>* owning_vector) { + auto non_owning_vector = new std::vector<T*>(); + for (auto& element : *owning_vector) { + non_owning_vector->push_back(element.get()); + } + return non_owning_vector; +} + +std::string ProfileCompilationInfo::DumpInfo( + const std::vector<std::unique_ptr<const DexFile>>* dex_files, + bool print_full_dex_location) const { + std::unique_ptr<const std::vector<const DexFile*>> non_owning_dex_files( + MakeNonOwningVector(dex_files)); + return DumpInfo(non_owning_dex_files.get(), print_full_dex_location); +} + std::string ProfileCompilationInfo::DumpInfo(const std::vector<const DexFile*>* dex_files, bool print_full_dex_location) const { std::ostringstream os; diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h index f8061bcfd8..b1587c0070 100644 --- a/runtime/jit/profile_compilation_info.h +++ b/runtime/jit/profile_compilation_info.h @@ -17,6 +17,7 @@ #ifndef ART_RUNTIME_JIT_PROFILE_COMPILATION_INFO_H_ #define ART_RUNTIME_JIT_PROFILE_COMPILATION_INFO_H_ +#include <memory> #include <set> #include <vector> @@ -72,6 +73,8 @@ class ProfileCompilationInfo { // If dex_files is not null then the method indices will be resolved to their // names. // This is intended for testing and debugging. + std::string DumpInfo(const std::vector<std::unique_ptr<const DexFile>>* dex_files, + bool print_full_dex_location = true) const; std::string DumpInfo(const std::vector<const DexFile*>* dex_files, bool print_full_dex_location = true) const; diff --git a/runtime/mirror/string.h b/runtime/mirror/string.h index 95b6c3e76b..409c6c2896 100644 --- a/runtime/mirror/string.h +++ b/runtime/mirror/string.h @@ -241,8 +241,9 @@ class MANAGED String FINAL : public Object { REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_); // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses". - // First bit (uppermost/leftmost) is taken out for Compressed/Uncompressed flag - // [0] Uncompressed: string uses 16-bit memory | [1] Compressed: 8-bit memory + + // If string compression is enabled, count_ holds the StringCompressionFlag in the + // least significant bit and the length in the remaining bits, length = count_ >> 1. int32_t count_; uint32_t hash_code_; diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc index 00d4144415..f8f8fa6b25 100644 --- a/runtime/openjdkjvmti/ti_thread.cc +++ b/runtime/openjdkjvmti/ti_thread.cc @@ -78,7 +78,9 @@ struct ThreadCallback : public art::ThreadLifecycleCallback, public art::Runtime if (art::kIsDebugBuild) { std::string name; self->GetThreadName(name); - if (name != "Signal Catcher" && !android::base::StartsWith(name, "Jit thread pool")) { + if (name != "JDWP" && + name != "Signal Catcher" && + !android::base::StartsWith(name, "Jit thread pool")) { LOG(FATAL) << "Unexpected thread before start: " << name; } } diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc index d13436ebf6..e659ea3bfb 100644 --- a/test/912-classes/classes.cc +++ b/test/912-classes/classes.cc @@ -17,9 +17,14 @@ #include <stdio.h> #include "base/macros.h" +#include "class_linker.h" #include "jni.h" +#include "mirror/class_loader.h" #include "openjdkjvmti/jvmti.h" +#include "runtime.h" #include "ScopedLocalRef.h" +#include "ScopedUtfChars.h" +#include "scoped_thread_state_change-inl.h" #include "thread-inl.h" #include "ti-agent/common_helper.h" @@ -278,69 +283,11 @@ static std::string GetClassName(jvmtiEnv* jenv, JNIEnv* jni_env, jclass klass) { return tmp; } -static std::string GetThreadName(jvmtiEnv* jenv, JNIEnv* jni_env, jthread thread) { - jvmtiThreadInfo info; - jvmtiError result = jenv->GetThreadInfo(thread, &info); - if (result != JVMTI_ERROR_NONE) { - if (jni_env != nullptr) { - JvmtiErrorToException(jni_env, result); - } else { - printf("Failed to get thread name.\n"); - } - return ""; - } - - std::string tmp(info.name); - jenv->Deallocate(reinterpret_cast<unsigned char*>(info.name)); - jni_env->DeleteLocalRef(info.context_class_loader); - jni_env->DeleteLocalRef(info.thread_group); - - return tmp; -} - -static std::string GetThreadName(Thread* thread) { - std::string tmp; - thread->GetThreadName(tmp); - return tmp; -} - -static void JNICALL ClassPrepareCallback(jvmtiEnv* jenv, - JNIEnv* jni_env, - jthread thread, - jclass klass) { - std::string name = GetClassName(jenv, jni_env, klass); - if (name == "") { - return; - } - std::string thread_name = GetThreadName(jenv, jni_env, thread); - if (thread_name == "") { - return; - } - std::string cur_thread_name = GetThreadName(Thread::Current()); - printf("Prepare: %s on %s (cur=%s)\n", - name.c_str(), - thread_name.c_str(), - cur_thread_name.c_str()); -} - -static void JNICALL ClassLoadCallback(jvmtiEnv* jenv, - JNIEnv* jni_env, - jthread thread, - jclass klass) { - std::string name = GetClassName(jenv, jni_env, klass); - if (name == "") { - return; - } - std::string thread_name = GetThreadName(jenv, jni_env, thread); - if (thread_name == "") { - return; - } - printf("Load: %s on %s\n", name.c_str(), thread_name.c_str()); -} - -extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadEvents( - JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) { - if (b == JNI_FALSE) { +static void EnableEvents(JNIEnv* env, + jboolean enable, + decltype(jvmtiEventCallbacks().ClassLoad) class_load, + decltype(jvmtiEventCallbacks().ClassPrepare) class_prepare) { + if (enable == JNI_FALSE) { jvmtiError ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_CLASS_LOAD, nullptr); @@ -356,8 +303,8 @@ extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadEvents( jvmtiEventCallbacks callbacks; memset(&callbacks, 0, sizeof(jvmtiEventCallbacks)); - callbacks.ClassLoad = ClassLoadCallback; - callbacks.ClassPrepare = ClassPrepareCallback; + callbacks.ClassLoad = class_load; + callbacks.ClassPrepare = class_prepare; jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)); if (JvmtiErrorToException(env, ret)) { return; @@ -375,5 +322,113 @@ extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadEvents( JvmtiErrorToException(env, ret); } +class ClassLoadPreparePrinter { + public: + static void JNICALL ClassLoadCallback(jvmtiEnv* jenv, + JNIEnv* jni_env, + jthread thread, + jclass klass) { + std::string name = GetClassName(jenv, jni_env, klass); + if (name == "") { + return; + } + std::string thread_name = GetThreadName(jenv, jni_env, thread); + if (thread_name == "") { + return; + } + printf("Load: %s on %s\n", name.c_str(), thread_name.c_str()); + } + + static void JNICALL ClassPrepareCallback(jvmtiEnv* jenv, + JNIEnv* jni_env, + jthread thread, + jclass klass) { + std::string name = GetClassName(jenv, jni_env, klass); + if (name == "") { + return; + } + std::string thread_name = GetThreadName(jenv, jni_env, thread); + if (thread_name == "") { + return; + } + std::string cur_thread_name = GetThreadName(Thread::Current()); + printf("Prepare: %s on %s (cur=%s)\n", + name.c_str(), + thread_name.c_str(), + cur_thread_name.c_str()); + } + + private: + static std::string GetThreadName(jvmtiEnv* jenv, JNIEnv* jni_env, jthread thread) { + jvmtiThreadInfo info; + jvmtiError result = jenv->GetThreadInfo(thread, &info); + if (result != JVMTI_ERROR_NONE) { + if (jni_env != nullptr) { + JvmtiErrorToException(jni_env, result); + } else { + printf("Failed to get thread name.\n"); + } + return ""; + } + + std::string tmp(info.name); + jenv->Deallocate(reinterpret_cast<unsigned char*>(info.name)); + jni_env->DeleteLocalRef(info.context_class_loader); + jni_env->DeleteLocalRef(info.thread_group); + + return tmp; + } + + static std::string GetThreadName(Thread* thread) { + std::string tmp; + thread->GetThreadName(tmp); + return tmp; + } +}; + +extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadPreparePrintEvents( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean enable) { + EnableEvents(env, + enable, + ClassLoadPreparePrinter::ClassLoadCallback, + ClassLoadPreparePrinter::ClassPrepareCallback); +} + +struct ClassLoadSeen { + static void JNICALL ClassLoadSeenCallback(jvmtiEnv* jenv ATTRIBUTE_UNUSED, + JNIEnv* jni_env ATTRIBUTE_UNUSED, + jthread thread ATTRIBUTE_UNUSED, + jclass klass ATTRIBUTE_UNUSED) { + saw_event = true; + } + + static bool saw_event; +}; +bool ClassLoadSeen::saw_event = false; + +extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadSeenEvents( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) { + EnableEvents(env, b, ClassLoadSeen::ClassLoadSeenCallback, nullptr); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_Main_hadLoadEvent( + JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED) { + return ClassLoadSeen::saw_event ? JNI_TRUE : JNI_FALSE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_Main_isLoadedClass( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jstring class_name) { + ScopedUtfChars name(env, class_name); + ScopedObjectAccess soa(Thread::Current()); + Runtime* current = Runtime::Current(); + ClassLinker* class_linker = current->GetClassLinker(); + bool found = + class_linker->LookupClass( + soa.Self(), + name.c_str(), + soa.Decode<mirror::ClassLoader>(current->GetSystemClassLoader())) != nullptr; + return found ? JNI_TRUE : JNI_FALSE; +} + } // namespace Test912Classes } // namespace art diff --git a/test/912-classes/src/Main.java b/test/912-classes/src/Main.java index 6ad23a4869..e3aceb9a5f 100644 --- a/test/912-classes/src/Main.java +++ b/test/912-classes/src/Main.java @@ -219,6 +219,15 @@ public class Main { } final ClassLoader boot = cl; + // The JIT may deeply inline and load some classes. Preload these for test determinism. + final String PRELOAD_FOR_JIT[] = { + "java.nio.charset.CoderMalfunctionError", + "java.util.NoSuchElementException" + }; + for (String s : PRELOAD_FOR_JIT) { + Class.forName(s); + } + Runnable r = new Runnable() { @Override public void run() { @@ -238,7 +247,7 @@ public class Main { ensureJitCompiled(Main.class, "testClassEvents"); - enableClassLoadEvents(true); + enableClassLoadPreparePrintEvents(true); ClassLoader cl1 = create(boot, DEX1, DEX2); System.out.println("B, false"); @@ -270,7 +279,37 @@ public class Main { t.start(); t.join(); - enableClassLoadEvents(false); + enableClassLoadPreparePrintEvents(false); + + // Note: the JIT part of this test is about the JIT pulling in a class not yet touched by + // anything else in the system. This could be the verifier or the interpreter. We + // block the interpreter by calling ensureJitCompiled. The verifier, however, must + // run in configurations where dex2oat didn't verify the class itself. So explicitly + // check whether the class has been already loaded, and skip then. + // TODO: Add multiple configurations to the run script once that becomes easier to do. + if (hasJit() && !isLoadedClass("Main$ClassD")) { + testClassEventsJit(); + } + } + + private static void testClassEventsJit() throws Exception { + enableClassLoadSeenEvents(true); + + testClassEventsJitImpl(); + + enableClassLoadSeenEvents(false); + + if (!hadLoadEvent()) { + throw new RuntimeException("Did not get expected load event."); + } + } + + private static void testClassEventsJitImpl() throws Exception { + ensureJitCompiled(Main.class, "testClassEventsJitImpl"); + + if (ClassD.x != 1) { + throw new RuntimeException("Unexpected value"); + } } private static void printClassLoaderClasses(ClassLoader cl) { @@ -335,9 +374,14 @@ public class Main { private static native int[] getClassVersion(Class<?> c); - private static native void enableClassLoadEvents(boolean b); + private static native void enableClassLoadPreparePrintEvents(boolean b); + + private static native void ensureJitCompiled(Class<?> c, String name); - private static native void ensureJitCompiled(Class c, String name); + private static native boolean hasJit(); + private static native boolean isLoadedClass(String name); + private static native void enableClassLoadSeenEvents(boolean b); + private static native boolean hadLoadEvent(); private static class TestForNonInit { public static double dummy = Math.random(); // So it can't be compile-time initialized. @@ -361,6 +405,10 @@ public class Main { public abstract static class ClassC implements InfA, InfC { } + public static class ClassD { + static int x = 1; + } + private static final String DEX1 = System.getenv("DEX_LOCATION") + "/912-classes.jar"; private static final String DEX2 = System.getenv("DEX_LOCATION") + "/912-classes-ex.jar"; diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index b937c931e7..1938b92db8 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -540,7 +540,6 @@ TEST_ART_BROKEN_JIT_RUN_TESTS := \ 629-vdex-speed \ 904-object-allocation \ 906-iterate-heap \ - 912-classes \ ifneq (,$(filter jit,$(COMPILER_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ diff --git a/tools/ahat/src/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java index 94934a2831..a062afdaef 100644 --- a/tools/ahat/src/InstanceUtils.java +++ b/tools/ahat/src/InstanceUtils.java @@ -26,6 +26,7 @@ import com.android.tools.perflib.heap.RootObj; import com.android.tools.perflib.heap.Type; import java.awt.image.BufferedImage; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -87,22 +88,27 @@ class InstanceUtils { // is a char[], use that directly as the value, otherwise use the value // field of the string object. The field accesses for count and offset // later on will work okay regardless of what type the inst object is. - Object value = inst; - if (isInstanceOfClass(inst, "java.lang.String")) { - value = getField(inst, "value"); - } + boolean isString = isInstanceOfClass(inst, "java.lang.String"); + Object value = isString ? getField(inst, "value") : inst; if (!(value instanceof ArrayInstance)) { return null; } ArrayInstance chars = (ArrayInstance) value; + int numChars = chars.getLength(); + int offset = getIntField(inst, "offset", 0); + int count = getIntField(inst, "count", numChars); + + // With string compression enabled, the array type can be BYTE but in that case + // offset must be 0 and count must match numChars. + if (isString && (chars.getArrayType() == Type.BYTE) && (offset == 0) && (count == numChars)) { + int length = (0 <= maxChars && maxChars < numChars) ? maxChars : numChars; + return new String(chars.asRawByteArray(/* offset */ 0, length), StandardCharsets.US_ASCII); + } if (chars.getArrayType() != Type.CHAR) { return null; } - - int numChars = chars.getLength(); - int count = getIntField(inst, "count", numChars); if (count == 0) { return ""; } @@ -110,7 +116,6 @@ class InstanceUtils { count = maxChars; } - int offset = getIntField(inst, "offset", 0); int end = offset + count - 1; if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) { return new String(chars.asCharArray(offset, count)); diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java index e08df67b13..587d9defef 100644 --- a/tools/ahat/test-dump/Main.java +++ b/tools/ahat/test-dump/Main.java @@ -45,6 +45,8 @@ public class Main { // class and reading the desired field. public static class DumpedStuff { public String basicString = "hello, world"; + public String nonAscii = "Sigma (\u01a9) is not ASCII"; + public String embeddedZero = "embedded\0..."; // Non-ASCII for string compression purposes. public char[] charArray = "char thing".toCharArray(); public String nullString = null; public Object anObject = new Object(); diff --git a/tools/ahat/test/InstanceUtilsTest.java b/tools/ahat/test/InstanceUtilsTest.java index ec77e70da1..fe2706d7d4 100644 --- a/tools/ahat/test/InstanceUtilsTest.java +++ b/tools/ahat/test/InstanceUtilsTest.java @@ -37,6 +37,20 @@ public class InstanceUtilsTest { } @Test + public void asStringNonAscii() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("nonAscii"); + assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str)); + } + + @Test + public void asStringEmbeddedZero() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("embeddedZero"); + assertEquals("embedded\0...", InstanceUtils.asString(str)); + } + + @Test public void asStringCharArray() throws IOException { TestDump dump = TestDump.getTestDump(); Instance str = (Instance)dump.getDumpedThing("charArray"); @@ -51,6 +65,20 @@ public class InstanceUtilsTest { } @Test + public void asStringTruncatedNonAscii() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("nonAscii"); + assertEquals("Sigma (\u01a9)", InstanceUtils.asString(str, 9)); + } + + @Test + public void asStringTruncatedEmbeddedZero() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("embeddedZero"); + assertEquals("embed", InstanceUtils.asString(str, 5)); + } + + @Test public void asStringCharArrayTruncated() throws IOException { TestDump dump = TestDump.getTestDump(); Instance str = (Instance)dump.getDumpedThing("charArray"); @@ -65,6 +93,20 @@ public class InstanceUtilsTest { } @Test + public void asStringExactMaxNonAscii() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("nonAscii"); + assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, 22)); + } + + @Test + public void asStringExactMaxEmbeddedZero() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("embeddedZero"); + assertEquals("embedded\0...", InstanceUtils.asString(str, 12)); + } + + @Test public void asStringCharArrayExactMax() throws IOException { TestDump dump = TestDump.getTestDump(); Instance str = (Instance)dump.getDumpedThing("charArray"); @@ -79,6 +121,20 @@ public class InstanceUtilsTest { } @Test + public void asStringNotTruncatedNonAscii() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("nonAscii"); + assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, 50)); + } + + @Test + public void asStringNotTruncatedEmbeddedZero() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("embeddedZero"); + assertEquals("embedded\0...", InstanceUtils.asString(str, 50)); + } + + @Test public void asStringCharArrayNotTruncated() throws IOException { TestDump dump = TestDump.getTestDump(); Instance str = (Instance)dump.getDumpedThing("charArray"); @@ -93,6 +149,20 @@ public class InstanceUtilsTest { } @Test + public void asStringNegativeMaxNonAscii() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("nonAscii"); + assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, -3)); + } + + @Test + public void asStringNegativeMaxEmbeddedZero() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("embeddedZero"); + assertEquals("embedded\0...", InstanceUtils.asString(str, -3)); + } + + @Test public void asStringCharArrayNegativeMax() throws IOException { TestDump dump = TestDump.getTestDump(); Instance str = (Instance)dump.getDumpedThing("charArray"); diff --git a/tools/run-libcore-tests.sh b/tools/run-libcore-tests.sh index 41faa69c31..729a3e5ac4 100755 --- a/tools/run-libcore-tests.sh +++ b/tools/run-libcore-tests.sh @@ -123,7 +123,7 @@ done vogar_args="$vogar_args --timeout 480" # Use Jack with "1.8" configuration. -vogar_args="$vogar_args --toolchain jack --language JN" +vogar_args="$vogar_args --toolchain jack --language JO" # JIT settings. if $use_jit; then |