From e0fe7ae36180863e45cbb9d1e6e9c30b1b1a949c Mon Sep 17 00:00:00 2001 From: Nicolas Geoffray Date: Mon, 9 Mar 2015 10:02:49 +0000 Subject: Make the SSA builder honor the debuggable flag. This requires to properly type phis that are only used by environments, and discard phis with incomptable types. The code generators do not handle these conflicting types. In the process, ensure a phi has a type that does not depend on the order of the inputs (for example (char, short) -> short), and set int for int-like types. We can refine this later. Change-Id: I60ab601d6d00b1cbf18623ee4ff1795aa28f84a1 --- compiler/optimizing/graph_checker.cc | 6 + compiler/optimizing/inliner.cc | 8 +- compiler/optimizing/nodes.h | 23 ++- compiler/optimizing/optimizing_compiler.cc | 7 +- compiler/optimizing/primitive_type_propagation.cc | 2 +- compiler/optimizing/ssa_builder.cc | 227 ++++++++++++++++++++-- compiler/optimizing/ssa_builder.h | 20 ++ test/457-regs/expected.txt | 0 test/457-regs/info.txt | 1 + test/457-regs/regs_jni.cc | 150 ++++++++++++++ test/457-regs/smali/PhiLiveness.smali | 82 ++++++++ test/457-regs/src/Main.java | 45 +++++ test/Android.libarttest.mk | 3 +- test/Android.run-test.mk | 15 +- 14 files changed, 556 insertions(+), 33 deletions(-) create mode 100644 test/457-regs/expected.txt create mode 100644 test/457-regs/info.txt create mode 100644 test/457-regs/regs_jni.cc create mode 100644 test/457-regs/smali/PhiLiveness.smali create mode 100644 test/457-regs/src/Main.java diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc index a7f1f74..76b9f4f 100644 --- a/compiler/optimizing/graph_checker.cc +++ b/compiler/optimizing/graph_checker.cc @@ -362,6 +362,12 @@ void SSAChecker::VisitPhi(HPhi* phi) { Primitive::PrettyDescriptor(phi->GetType()))); } } + if (phi->GetType() != HPhi::ToPhiType(phi->GetType())) { + AddError(StringPrintf("Phi %d in block %d does not have an expected phi type: %s", + phi->GetId(), + phi->GetBlock()->GetBlockId(), + Primitive::PrettyDescriptor(phi->GetType()))); + } } void SSAChecker::VisitIf(HIf* instruction) { diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index b34957a..e22f7cc 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -124,8 +124,8 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, resolved_method->GetAccessFlags(), nullptr); - HGraph* callee_graph = - new (graph_->GetArena()) HGraph(graph_->GetArena(), graph_->GetCurrentInstructionId()); + HGraph* callee_graph = new (graph_->GetArena()) HGraph( + graph_->GetArena(), graph_->IsDebuggable(), graph_->GetCurrentInstructionId()); OptimizingCompilerStats inline_stats; HGraphBuilder builder(callee_graph, @@ -155,15 +155,11 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, } // Run simple optimizations on the graph. - SsaRedundantPhiElimination redundant_phi(callee_graph); - SsaDeadPhiElimination dead_phi(callee_graph); HDeadCodeElimination dce(callee_graph); HConstantFolding fold(callee_graph); InstructionSimplifier simplify(callee_graph, stats_); HOptimization* optimizations[] = { - &redundant_phi, - &dead_phi, &dce, &fold, &simplify, diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 8b56166..942aa23 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -103,7 +103,7 @@ class HInstructionList { // Control-flow graph of a method. Contains a list of basic blocks. class HGraph : public ArenaObject { public: - HGraph(ArenaAllocator* arena, int start_instruction_id = 0) + HGraph(ArenaAllocator* arena, bool debuggable = false, int start_instruction_id = 0) : arena_(arena), blocks_(arena, kDefaultNumberOfBlocks), reverse_post_order_(arena, kDefaultNumberOfBlocks), @@ -114,6 +114,7 @@ class HGraph : public ArenaObject { number_of_in_vregs_(0), temporaries_vreg_slots_(0), has_array_accesses_(false), + debuggable_(debuggable), current_instruction_id_(start_instruction_id) {} ArenaAllocator* GetArena() const { return arena_; } @@ -208,6 +209,8 @@ class HGraph : public ArenaObject { has_array_accesses_ = value; } + bool IsDebuggable() const { return debuggable_; } + HNullConstant* GetNullConstant(); private: @@ -248,6 +251,11 @@ class HGraph : public ArenaObject { // Has array accesses. We can totally skip BCE if it's false. bool has_array_accesses_; + // Indicates whether the graph should be compiled in a way that + // ensures full debuggability. If false, we can apply more + // aggressive optimizations that may limit the level of debugging. + const bool debuggable_; + // The current id to assign to a newly added instruction. See HInstruction.id_. int32_t current_instruction_id_; @@ -2498,6 +2506,19 @@ class HPhi : public HInstruction { inputs_.SetSize(number_of_inputs); } + // Returns a type equivalent to the given `type`, but that a `HPhi` can hold. + static Primitive::Type ToPhiType(Primitive::Type type) { + switch (type) { + case Primitive::kPrimBoolean: + case Primitive::kPrimByte: + case Primitive::kPrimShort: + case Primitive::kPrimChar: + return Primitive::kPrimInt; + default: + return type; + } + } + size_t InputCount() const OVERRIDE { return inputs_.Size(); } void AddInput(HInstruction* input); diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index eb98424..3470595 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -298,8 +298,6 @@ static void RunOptimizations(HGraph* graph, const DexCompilationUnit& dex_compilation_unit, PassInfoPrinter* pass_info_printer, StackHandleScopeCollection* handles) { - SsaRedundantPhiElimination redundant_phi(graph); - SsaDeadPhiElimination dead_phi(graph); HDeadCodeElimination dce(graph); HConstantFolding fold1(graph); InstructionSimplifier simplify1(graph, stats); @@ -317,8 +315,6 @@ static void RunOptimizations(HGraph* graph, IntrinsicsRecognizer intrinsics(graph, dex_compilation_unit.GetDexFile(), driver); HOptimization* optimizations[] = { - &redundant_phi, - &dead_phi, &intrinsics, &dce, &fold1, @@ -461,7 +457,8 @@ CompiledMethod* OptimizingCompiler::Compile(const DexFile::CodeItem* code_item, ArenaPool pool; ArenaAllocator arena(&pool); - HGraph* graph = new (&arena) HGraph(&arena); + HGraph* graph = new (&arena) HGraph( + &arena, compiler_driver->GetCompilerOptions().GetDebuggable()); // For testing purposes, we put a special marker on method names that should be compiled // with this compiler. This makes sure we're not regressing. diff --git a/compiler/optimizing/primitive_type_propagation.cc b/compiler/optimizing/primitive_type_propagation.cc index fe23fcf..c20c8a1 100644 --- a/compiler/optimizing/primitive_type_propagation.cc +++ b/compiler/optimizing/primitive_type_propagation.cc @@ -33,7 +33,7 @@ static Primitive::Type MergeTypes(Primitive::Type existing, Primitive::Type new_ // to merge with a void type, we should use the existing one. return new_type == Primitive::kPrimVoid ? existing - : new_type; + : HPhi::ToPhiType(new_type); } } diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc index 3dc7505..1a8e784 100644 --- a/compiler/optimizing/ssa_builder.cc +++ b/compiler/optimizing/ssa_builder.cc @@ -22,6 +22,158 @@ namespace art { +/** + * A debuggable application may require to reviving phis, to ensure their + * associated DEX register is available to a debugger. This class implements + * the logic for statement (c) of the SsaBuilder (see ssa_builder.h). It + * also makes sure that phis with incompatible input types are not revived + * (statement (b) of the SsaBuilder). + * + * This phase must be run after detecting dead phis through the + * DeadPhiElimination phase, and before deleting the dead phis. + */ +class DeadPhiHandling : public ValueObject { + public: + explicit DeadPhiHandling(HGraph* graph) + : graph_(graph), worklist_(graph->GetArena(), kDefaultWorklistSize) {} + + void Run(); + + private: + void VisitBasicBlock(HBasicBlock* block); + void ProcessWorklist(); + void AddToWorklist(HPhi* phi); + void AddDependentInstructionsToWorklist(HPhi* phi); + bool UpdateType(HPhi* phi); + + HGraph* const graph_; + GrowableArray worklist_; + + static constexpr size_t kDefaultWorklistSize = 8; + + DISALLOW_COPY_AND_ASSIGN(DeadPhiHandling); +}; + +bool DeadPhiHandling::UpdateType(HPhi* phi) { + Primitive::Type existing = phi->GetType(); + DCHECK(phi->IsLive()); + + bool conflict = false; + Primitive::Type new_type = existing; + for (size_t i = 0, e = phi->InputCount(); i < e; ++i) { + HInstruction* input = phi->InputAt(i); + if (input->IsPhi() && input->AsPhi()->IsDead()) { + // We are doing a reverse post order visit of the graph, reviving + // phis that have environment uses and updating their types. If an + // input is a phi, and it is dead (because its input types are + // conflicting), this phi must be marked dead as well. + conflict = true; + break; + } + Primitive::Type input_type = HPhi::ToPhiType(input->GetType()); + + // The only acceptable transitions are: + // - From void to typed: first time we update the type of this phi. + // - From int to reference (or reference to int): the phi has to change + // to reference type. If the integer input cannot be converted to a + // reference input, the phi will remain dead. + if (new_type == Primitive::kPrimVoid) { + new_type = input_type; + } else if (new_type == Primitive::kPrimNot && input_type == Primitive::kPrimInt) { + HInstruction* equivalent = SsaBuilder::GetReferenceTypeEquivalent(input); + if (equivalent == nullptr) { + conflict = true; + break; + } else { + phi->ReplaceInput(equivalent, i); + if (equivalent->IsPhi()) { + DCHECK_EQ(equivalent->GetType(), Primitive::kPrimNot); + // We created a new phi, but that phi has the same inputs as the old phi. We + // add it to the worklist to ensure its inputs can also be converted to reference. + // If not, it will remain dead, and the algorithm will make the current phi dead + // as well. + equivalent->AsPhi()->SetLive(); + AddToWorklist(equivalent->AsPhi()); + } + } + } else if (new_type == Primitive::kPrimInt && input_type == Primitive::kPrimNot) { + new_type = Primitive::kPrimNot; + // Start over, we may request reference equivalents for the inputs of the phi. + i = -1; + } else if (new_type != input_type) { + conflict = true; + break; + } + } + + if (conflict) { + phi->SetType(Primitive::kPrimVoid); + phi->SetDead(); + return true; + } else { + DCHECK(phi->IsLive()); + phi->SetType(new_type); + return existing != new_type; + } +} + +void DeadPhiHandling::VisitBasicBlock(HBasicBlock* block) { + for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) { + HPhi* phi = it.Current()->AsPhi(); + if (phi->IsDead() && phi->HasEnvironmentUses()) { + phi->SetLive(); + if (block->IsLoopHeader()) { + // Give a type to the loop phi, to guarantee convergence of the algorithm. + phi->SetType(phi->InputAt(0)->GetType()); + AddToWorklist(phi); + } else { + // Because we are doing a reverse post order visit, all inputs of + // this phi have been visited and therefore had their (initial) type set. + UpdateType(phi); + } + } + } +} + +void DeadPhiHandling::ProcessWorklist() { + while (!worklist_.IsEmpty()) { + HPhi* instruction = worklist_.Pop(); + // Note that the same equivalent phi can be added multiple times in the work list, if + // used by multiple phis. The first call to `UpdateType` will know whether the phi is + // dead or live. + if (instruction->IsLive() && UpdateType(instruction)) { + AddDependentInstructionsToWorklist(instruction); + } + } +} + +void DeadPhiHandling::AddToWorklist(HPhi* instruction) { + DCHECK(instruction->IsLive()); + worklist_.Add(instruction); +} + +void DeadPhiHandling::AddDependentInstructionsToWorklist(HPhi* instruction) { + for (HUseIterator it(instruction->GetUses()); !it.Done(); it.Advance()) { + HPhi* phi = it.Current()->GetUser()->AsPhi(); + if (phi != nullptr && !phi->IsDead()) { + AddToWorklist(phi); + } + } +} + +void DeadPhiHandling::Run() { + for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) { + VisitBasicBlock(it.Current()); + } + ProcessWorklist(); +} + +static bool IsPhiEquivalentOf(HInstruction* instruction, HPhi* phi) { + return instruction != nullptr + && instruction->IsPhi() + && instruction->AsPhi()->GetRegNumber() == phi->GetRegNumber(); +} + void SsaBuilder::BuildSsa() { // 1) Visit in reverse post order. We need to have all predecessors of a block visited // (with the exception of loops) in order to create the right environment for that @@ -47,11 +199,9 @@ void SsaBuilder::BuildSsa() { // our code generator will complain if the inputs of a phi do not have the same // type. The marking allows the type propagation to know which phis it needs // to handle. We mark but do not eliminate: the elimination will be done in - // step 5). - { - SsaDeadPhiElimination dead_phis(GetGraph()); - dead_phis.MarkDeadPhis(); - } + // step 8). + SsaDeadPhiElimination dead_phis(GetGraph()); + dead_phis.MarkDeadPhis(); // 4) Propagate types of phis. At this point, phis are typed void in the general // case, or float/double/reference when we created an equivalent phi. So we @@ -59,17 +209,58 @@ void SsaBuilder::BuildSsa() { PrimitiveTypePropagation type_propagation(GetGraph()); type_propagation.Run(); - // 5) Step 4) changes inputs of phis which may lead to dead phis again. We re-run - // the algorithm and this time elimimates them. - // TODO: Make this work with debug info and reference liveness. We currently - // eagerly remove phis used in environments. - { - SsaDeadPhiElimination dead_phis(GetGraph()); - dead_phis.Run(); + // 5) Now that the graph is correclty typed, we can get rid of redundant phis. + // Note that we cannot do this phase before type propagation, otherwise + // we could get rid of phi equivalents, whose presence is a requirement for the + // type propagation phase. Note that this is to satisfy statement (a) of the + // SsaBuilder (see ssa_builder.h). + SsaRedundantPhiElimination redundant_phi(GetGraph()); + redundant_phi.Run(); + + // 6) Make sure environments use the right phi "equivalent": a phi marked dead + // can have a phi equivalent that is not dead. We must therefore update + // all environment uses of the dead phi to use its equivalent. Note that there + // can be multiple phis for the same Dex register that are live (for example + // when merging constants), in which case it is OK for the environments + // to just reference one. + for (HReversePostOrderIterator it(*GetGraph()); !it.Done(); it.Advance()) { + HBasicBlock* block = it.Current(); + for (HInstructionIterator it_phis(block->GetPhis()); !it_phis.Done(); it_phis.Advance()) { + HPhi* phi = it_phis.Current()->AsPhi(); + // If the phi is not dead, or has no environment uses, there is nothing to do. + if (!phi->IsDead() || !phi->HasEnvironmentUses()) continue; + HInstruction* next = phi->GetNext(); + if (!IsPhiEquivalentOf(next, phi)) continue; + if (next->AsPhi()->IsDead()) { + // If the phi equivalent is dead, check if there is another one. + next = next->GetNext(); + if (!IsPhiEquivalentOf(next, phi)) continue; + // There can be at most two phi equivalents. + DCHECK(!IsPhiEquivalentOf(next->GetNext(), phi)); + if (next->AsPhi()->IsDead()) continue; + } + // We found a live phi equivalent. Update the environment uses of `phi` with it. + phi->ReplaceWith(next); + } } - // 6) Clear locals. - // TODO: Move this to a dead code eliminator phase. + // 7) Deal with phis to guarantee liveness of phis in case of a debuggable + // application. This is for satisfying statement (c) of the SsaBuilder + // (see ssa_builder.h). + if (GetGraph()->IsDebuggable()) { + DeadPhiHandling dead_phi_handler(GetGraph()); + dead_phi_handler.Run(); + } + + // 8) Now that the right phis are used for the environments, and we + // have potentially revive dead phis in case of a debuggable application, + // we can eliminate phis we do not need. Regardless of the debuggable status, + // this phase is necessary for statement (b) of the SsaBuilder (see ssa_builder.h), + // as well as for the code generation, which does not deal with phis of conflicting + // input types. + dead_phis.EliminateDeadPhis(); + + // 9) Clear locals. for (HInstructionIterator it(GetGraph()->GetEntryBlock()->GetInstructions()); !it.Done(); it.Advance()) { @@ -257,12 +448,12 @@ HInstruction* SsaBuilder::GetFloatOrDoubleEquivalent(HInstruction* user, } HInstruction* SsaBuilder::GetReferenceTypeEquivalent(HInstruction* value) { - if (value->IsIntConstant()) { - DCHECK_EQ(value->AsIntConstant()->GetValue(), 0); + if (value->IsIntConstant() && value->AsIntConstant()->GetValue() == 0) { return value->GetBlock()->GetGraph()->GetNullConstant(); - } else { - DCHECK(value->IsPhi()); + } else if (value->IsPhi()) { return GetFloatDoubleOrReferenceEquivalentOfPhi(value->AsPhi(), Primitive::kPrimNot); + } else { + return nullptr; } } diff --git a/compiler/optimizing/ssa_builder.h b/compiler/optimizing/ssa_builder.h index f50da46..b414fb2 100644 --- a/compiler/optimizing/ssa_builder.h +++ b/compiler/optimizing/ssa_builder.h @@ -24,6 +24,26 @@ namespace art { static constexpr int kDefaultNumberOfLoops = 2; +/** + * Transforms a graph into SSA form. The liveness guarantees of + * this transformation are listed below. A DEX register + * being killed means its value at a given position in the code + * will not be available to its environment uses. A merge in the + * following text is materialized as a `HPhi`. + * + * (a) Dex registers that do not require merging (that is, they do not + * have different values at a join block) are available to all their + * environment uses. + * + * (b) Dex registers that require merging, and the merging gives + * incompatible types, will be killed for environment uses of that merge. + * + * (c) When the `debuggable` flag is passed to the compiler, Dex registers + * that require merging and have a proper type after the merge, are + * available to all their environment uses. If the `debuggable` flag + * is not set, values of Dex registers only used by environments + * are killed. + */ class SsaBuilder : public HGraphVisitor { public: explicit SsaBuilder(HGraph* graph) diff --git a/test/457-regs/expected.txt b/test/457-regs/expected.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/457-regs/info.txt b/test/457-regs/info.txt new file mode 100644 index 0000000..d950003 --- /dev/null +++ b/test/457-regs/info.txt @@ -0,0 +1 @@ +Tests debuggability of DEX registers. diff --git a/test/457-regs/regs_jni.cc b/test/457-regs/regs_jni.cc new file mode 100644 index 0000000..ce701e8 --- /dev/null +++ b/test/457-regs/regs_jni.cc @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "arch/context.h" +#include "jni.h" +#include "mirror/art_method-inl.h" +#include "scoped_thread_state_change.h" +#include "stack.h" +#include "thread.h" + +namespace art { + +namespace { + +class TestVisitor : public StackVisitor { + public: + TestVisitor(Thread* thread, Context* context) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) + : StackVisitor(thread, context) {} + + bool VisitFrame() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::ArtMethod* m = GetMethod(); + std::string m_name(m->GetName()); + + if (m_name.compare("mergeOk") == 0) { + uint32_t value = 0; + + CHECK(GetVReg(m, 0, kIntVReg, &value)); + CHECK_EQ(value, 0u); + + CHECK(GetVReg(m, 1, kIntVReg, &value)); + CHECK_EQ(value, 1u); + + CHECK(GetVReg(m, 2, kIntVReg, &value)); + CHECK_EQ(value, 2u); + + CHECK(GetVReg(m, 3, kIntVReg, &value)); + CHECK_EQ(value, 1u); + + CHECK(GetVReg(m, 4, kIntVReg, &value)); + CHECK_EQ(value, 2u); + did_check_ = true; + } else if (m_name.compare("mergeNotOk") == 0) { + uint32_t value = 0; + + CHECK(GetVReg(m, 0, kIntVReg, &value)); + CHECK_EQ(value, 0u); + + CHECK(GetVReg(m, 1, kIntVReg, &value)); + CHECK_EQ(value, 1u); + + bool success = GetVReg(m, 2, kIntVReg, &value); + if (m->IsOptimized(sizeof(void*))) CHECK(!success); + + CHECK(GetVReg(m, 3, kReferenceVReg, &value)); + CHECK_EQ(value, 1u); + + CHECK(GetVReg(m, 4, kFloatVReg, &value)); + uint32_t cast = bit_cast(4.0f); + CHECK_EQ(value, cast); + did_check_ = true; + } else if (m_name.compare("phiEquivalent") == 0) { + uint32_t value = 0; + + CHECK(GetVReg(m, 0, kIntVReg, &value)); + // Quick doesn't like this one on x64. + CHECK_EQ(value, 0u); + + CHECK(GetVReg(m, 1, kIntVReg, &value)); + CHECK_EQ(value, 1u); + + CHECK(GetVReg(m, 2, kFloatVReg, &value)); + CHECK_EQ(value, 1u); + + did_check_ = true; + } else if (m_name.compare("mergeReferences") == 0) { + uint32_t value = 0; + + CHECK(GetVReg(m, 0, kIntVReg, &value)); + CHECK_EQ(value, 0u); + + CHECK(GetVReg(m, 1, kIntVReg, &value)); + CHECK_EQ(value, 1u); + + CHECK(GetVReg(m, 2, kReferenceVReg, &value)); + CHECK_EQ(value, 0u); + + CHECK(GetVReg(m, 3, kReferenceVReg, &value)); + CHECK_NE(value, 0u); + + did_check_ = true; + } else if (m_name.compare("phiAllEquivalents") == 0) { + uint32_t value = 0; + + CHECK(GetVReg(m, 0, kIntVReg, &value)); + CHECK_EQ(value, 0u); + + CHECK(GetVReg(m, 1, kIntVReg, &value)); + CHECK_EQ(value, 1u); + + CHECK(GetVReg(m, 2, kReferenceVReg, &value)); + CHECK_EQ(value, 0u); + + did_check_ = true; + } + + return true; + } + + bool did_check_ = false; +}; + +extern "C" JNIEXPORT void JNICALL Java_PhiLiveness_regsNativeCall( + JNIEnv*, jclass value ATTRIBUTE_UNUSED) { + ScopedObjectAccess soa(Thread::Current()); + std::unique_ptr context(Context::Create()); + TestVisitor visitor(soa.Self(), context.get()); + visitor.WalkStack(); + CHECK(visitor.did_check_); +} + +extern "C" JNIEXPORT void JNICALL Java_PhiLiveness_regsNativeCallWithParameters( + JNIEnv*, jclass value ATTRIBUTE_UNUSED, jobject main, jint int_value, jfloat float_value) { + ScopedObjectAccess soa(Thread::Current()); + std::unique_ptr context(Context::Create()); + CHECK(soa.Decode(main) == nullptr); + CHECK_EQ(int_value, 0); + int32_t cast = bit_cast(float_value); + CHECK_EQ(cast, 0); + TestVisitor visitor(soa.Self(), context.get()); + visitor.WalkStack(); + CHECK(visitor.did_check_); +} + +} // namespace + +} // namespace art diff --git a/test/457-regs/smali/PhiLiveness.smali b/test/457-regs/smali/PhiLiveness.smali new file mode 100644 index 0000000..c8a6773 --- /dev/null +++ b/test/457-regs/smali/PhiLiveness.smali @@ -0,0 +1,82 @@ +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LPhiLiveness; + +.super Ljava/lang/Object; + +.method public static mergeOk(ZB)V + .registers 5 + const/4 v0, 0x0 + const/4 v1, 0x1 + move v2, v3 + if-eq v1, v0, :else + move v2, v4 + :else + invoke-static {}, LPhiLiveness;->regsNativeCall()V + return-void +.end method + +.method public static mergeNotOk(ZF)V + .registers 5 + const/4 v0, 0x0 + const/4 v1, 0x1 + move v2, v3 + if-eq v1, v0, :else + move v2, v4 + :else + invoke-static {}, LPhiLiveness;->regsNativeCall()V + return-void +.end method + +.method public static mergeReferences(LMain;)V + .registers 4 + const/4 v0, 0x0 + const/4 v1, 0x1 + move-object v2, p0 + if-eq v1, v0, :else + move v2, v0 + :else + invoke-static {}, LPhiLiveness;->regsNativeCall()V + return-void +.end method + +.method public static phiEquivalent()F + .registers 5 + const/4 v0, 0x0 + const/4 v1, 0x1 + move v2, v0 + if-eq v1, v0, :else + move v2, v1 + :else + invoke-static {}, LPhiLiveness;->regsNativeCall()V + return v2 +.end method + +.method public static phiAllEquivalents(LMain;)V + .registers 4 + const/4 v0, 0x0 + const/4 v1, 0x1 + move v2, v0 + if-eq v1, v0, :else + move v2, v0 + :else + invoke-static {v2, v2, v2}, LPhiLiveness;->regsNativeCallWithParameters(LMain;IF)V + return-void +.end method + +.method public static native regsNativeCall()V +.end method +.method public static native regsNativeCallWithParameters(LMain;IF)V +.end method diff --git a/test/457-regs/src/Main.java b/test/457-regs/src/Main.java new file mode 100644 index 0000000..0d82033 --- /dev/null +++ b/test/457-regs/src/Main.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.reflect.Method; + +public class Main { + + // Workaround for b/18051191. + class InnerClass {} + + public static void main(String[] args) throws Exception { + Class c = Class.forName("PhiLiveness"); + Method m = c.getMethod("mergeOk", boolean.class, byte.class); + m.invoke(null, new Boolean(true), new Byte((byte)2)); + + m = c.getMethod("mergeNotOk", boolean.class, float.class); + m.invoke(null, new Boolean(true), new Float(4.0f)); + + m = c.getMethod("mergeReferences", Main.class); + m.invoke(null, new Main()); + + m = c.getMethod("phiEquivalent"); + m.invoke(null); + + m = c.getMethod("phiAllEquivalents", Main.class); + m.invoke(null, new Main()); + } + + static { + System.loadLibrary("arttest"); + } +} diff --git a/test/Android.libarttest.mk b/test/Android.libarttest.mk index 75c5d72..f4bab3f 100644 --- a/test/Android.libarttest.mk +++ b/test/Android.libarttest.mk @@ -29,7 +29,8 @@ LIBARTTEST_COMMON_SRC_FILES := \ 117-nopatchoat/nopatchoat.cc \ 118-noimage-dex2oat/noimage-dex2oat.cc \ 454-get-vreg/get_vreg_jni.cc \ - 455-set-vreg/set_vreg_jni.cc + 455-set-vreg/set_vreg_jni.cc \ + 457-regs/regs_jni.cc ART_TARGET_LIBARTTEST_$(ART_PHONY_TEST_TARGET_SUFFIX) += $(ART_TARGET_TEST_OUT)/$(TARGET_ARCH)/libarttest.so ifdef TARGET_2ND_ARCH diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index da36056..740961e 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -321,7 +321,8 @@ endif TEST_ART_BROKEN_NDEBUG_TESTS := # Known broken tests for the default compiler (Quick). -TEST_ART_BROKEN_DEFAULT_RUN_TESTS := +TEST_ART_BROKEN_DEFAULT_RUN_TESTS := \ + 457-regs ifneq (,$(filter default,$(COMPILER_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ @@ -377,6 +378,18 @@ endif TEST_ART_BROKEN_OPTIMIZING_RUN_TESTS := +# Tests that should fail when the optimizing compiler compiles them non-debuggable. +TEST_ART_BROKEN_OPTIMIZING_NONDEBUGGABLE_RUN_TESTS := \ + 457-regs \ + +ifneq (,$(filter optimizing,$(COMPILER_TYPES))) + ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ + optimizing,$(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES), \ + $(IMAGE_TYPES),$(PICTEST_TYPES),nondebuggable,$(TEST_ART_BROKEN_OPTIMIZING_NONDEBUGGABLE_RUN_TESTS),$(ALL_ADDRESS_SIZES)) +endif + +TEST_ART_BROKEN_OPTIMIZING_NONDEBUGGABLE_RUN_TESTS := + # Clear variables ahead of appending to them when defining tests. $(foreach target, $(TARGET_TYPES), $(eval ART_RUN_TEST_$(call name-to-var,$(target))_RULES :=)) -- cgit v1.1