/* * 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 "instruction_simplifier_shared.h" #include "mirror/array-inl.h" namespace art { namespace { bool TrySimpleMultiplyAccumulatePatterns(HMul* mul, HBinaryOperation* input_binop, HInstruction* input_other) { DCHECK(DataType::IsIntOrLongType(mul->GetType())); DCHECK(input_binop->IsAdd() || input_binop->IsSub()); DCHECK_NE(input_binop, input_other); if (!input_binop->HasOnlyOneNonEnvironmentUse()) { return false; } // Try to interpret patterns like // a * (b <+/-> 1) // as // (a * b) <+/-> a HInstruction* input_a = input_other; HInstruction* input_b = nullptr; // Set to a non-null value if we found a pattern to optimize. HInstruction::InstructionKind op_kind; if (input_binop->IsAdd()) { if ((input_binop->GetConstantRight() != nullptr) && input_binop->GetConstantRight()->IsOne()) { // Interpret // a * (b + 1) // as // (a * b) + a input_b = input_binop->GetLeastConstantLeft(); op_kind = HInstruction::kAdd; } } else { DCHECK(input_binop->IsSub()); if (input_binop->GetRight()->IsConstant() && input_binop->GetRight()->AsConstant()->IsMinusOne()) { // Interpret // a * (b - (-1)) // as // a + (a * b) input_b = input_binop->GetLeft(); op_kind = HInstruction::kAdd; } else if (input_binop->GetLeft()->IsConstant() && input_binop->GetLeft()->AsConstant()->IsOne()) { // Interpret // a * (1 - b) // as // a - (a * b) input_b = input_binop->GetRight(); op_kind = HInstruction::kSub; } } if (input_b == nullptr) { // We did not find a pattern we can optimize. return false; } ArenaAllocator* allocator = mul->GetBlock()->GetGraph()->GetAllocator(); HMultiplyAccumulate* mulacc = new (allocator) HMultiplyAccumulate( mul->GetType(), op_kind, input_a, input_a, input_b, mul->GetDexPc()); mul->GetBlock()->ReplaceAndRemoveInstructionWith(mul, mulacc); input_binop->GetBlock()->RemoveInstruction(input_binop); return true; } } // namespace bool TryCombineMultiplyAccumulate(HMul* mul, InstructionSet isa) { DataType::Type type = mul->GetType(); switch (isa) { case InstructionSet::kArm: case InstructionSet::kThumb2: if (type != DataType::Type::kInt32) { return false; } break; case InstructionSet::kArm64: if (!DataType::IsIntOrLongType(type)) { return false; } break; default: return false; } ArenaAllocator* allocator = mul->GetBlock()->GetGraph()->GetAllocator(); if (mul->HasOnlyOneNonEnvironmentUse()) { HInstruction* use = mul->GetUses().front().GetUser(); if (use->IsAdd() || use->IsSub()) { // Replace code looking like // MUL tmp, x, y // SUB dst, acc, tmp // with // MULSUB dst, acc, x, y // Note that we do not want to (unconditionally) perform the merge when the // multiplication has multiple uses and it can be merged in all of them. // Multiple uses could happen on the same control-flow path, and we would // then increase the amount of work. In the future we could try to evaluate // whether all uses are on different control-flow paths (using dominance and // reverse-dominance information) and only perform the merge when they are. HInstruction* accumulator = nullptr; HBinaryOperation* binop = use->AsBinaryOperation(); HInstruction* binop_left = binop->GetLeft(); HInstruction* binop_right = binop->GetRight(); // Be careful after GVN. This should not happen since the `HMul` has only // one use. DCHECK_NE(binop_left, binop_right); if (binop_right == mul) { accumulator = binop_left; } else if (use->IsAdd()) { DCHECK_EQ(binop_left, mul); accumulator = binop_right; } if (accumulator != nullptr) { HMultiplyAccumulate* mulacc = new (allocator) HMultiplyAccumulate(type, binop->GetKind(), accumulator, mul->GetLeft(), mul->GetRight()); binop->GetBlock()->ReplaceAndRemoveInstructionWith(binop, mulacc); DCHECK(!mul->HasUses()); mul->GetBlock()->RemoveInstruction(mul); return true; } } else if (use->IsNeg() && isa != InstructionSet::kArm) { HMultiplyAccumulate* mulacc = new (allocator) HMultiplyAccumulate(type, HInstruction::kSub, mul->GetBlock()->GetGraph()->GetConstant(type, 0), mul->GetLeft(), mul->GetRight()); use->GetBlock()->ReplaceAndRemoveInstructionWith(use, mulacc); DCHECK(!mul->HasUses()); mul->GetBlock()->RemoveInstruction(mul); return true; } } // Use multiply accumulate instruction for a few simple patterns. // We prefer not applying the following transformations if the left and // right inputs perform the same operation. // We rely on GVN having squashed the inputs if appropriate. However the // results are still correct even if that did not happen. if (mul->GetLeft() == mul->GetRight()) { return false; } HInstruction* left = mul->GetLeft(); HInstruction* right = mul->GetRight(); if ((right->IsAdd() || right->IsSub()) && TrySimpleMultiplyAccumulatePatterns(mul, right->AsBinaryOperation(), left)) { return true; } if ((left->IsAdd() || left->IsSub()) && TrySimpleMultiplyAccumulatePatterns(mul, left->AsBinaryOperation(), right)) { return true; } return false; } bool TryMergeNegatedInput(HBinaryOperation* op) { DCHECK(op->IsAnd() || op->IsOr() || op->IsXor()) << op->DebugName(); HInstruction* left = op->GetLeft(); HInstruction* right = op->GetRight(); // Only consider the case where there is exactly one Not, with 2 Not's De // Morgan's laws should be applied instead. if (left->IsNot() ^ right->IsNot()) { HInstruction* hnot = (left->IsNot() ? left : right); HInstruction* hother = (left->IsNot() ? right : left); // Only do the simplification if the Not has only one use and can thus be // safely removed. Even though ARM64 negated bitwise operations do not have // an immediate variant (only register), we still do the simplification when // `hother` is a constant, because it removes an instruction if the constant // cannot be encoded as an immediate: // mov r0, #large_constant // neg r2, r1 // and r0, r0, r2 // becomes: // mov r0, #large_constant // bic r0, r0, r1 if (hnot->HasOnlyOneNonEnvironmentUse()) { // Replace code looking like // NOT tmp, mask // AND dst, src, tmp (respectively ORR, EOR) // with // BIC dst, src, mask (respectively ORN, EON) HInstruction* src = hnot->AsNot()->GetInput(); HBitwiseNegatedRight* neg_op = new (hnot->GetBlock()->GetGraph()->GetAllocator()) HBitwiseNegatedRight(op->GetType(), op->GetKind(), hother, src, op->GetDexPc()); op->GetBlock()->ReplaceAndRemoveInstructionWith(op, neg_op); hnot->GetBlock()->RemoveInstruction(hnot); return true; } } return false; } bool TryExtractArrayAccessAddress(HInstruction* access, HInstruction* array, HInstruction* index, size_t data_offset) { if (index->IsConstant() || (index->IsBoundsCheck() && index->AsBoundsCheck()->GetIndex()->IsConstant())) { // When the index is a constant all the addressing can be fitted in the // memory access instruction, so do not split the access. return false; } if (access->IsArraySet() && access->AsArraySet()->GetValue()->GetType() == DataType::Type::kReference) { // The access may require a runtime call or the original array pointer. return false; } if (kEmitCompilerReadBarrier && access->IsArrayGet() && access->GetType() == DataType::Type::kReference) { // For object arrays, the read barrier instrumentation requires // the original array pointer. // TODO: This can be relaxed for Baker CC. return false; } // Proceed to extract the base address computation. HGraph* graph = access->GetBlock()->GetGraph(); ArenaAllocator* allocator = graph->GetAllocator(); HIntConstant* offset = graph->GetIntConstant(data_offset); HIntermediateAddress* address = new (allocator) HIntermediateAddress(array, offset, kNoDexPc); // TODO: Is it ok to not have this on the intermediate address? // address->SetReferenceTypeInfo(array->GetReferenceTypeInfo()); access->GetBlock()->InsertInstructionBefore(address, access); access->ReplaceInput(address, 0); // Both instructions must depend on GC to prevent any instruction that can // trigger GC to be inserted between the two. access->AddSideEffects(SideEffects::DependsOnGC()); DCHECK(address->GetSideEffects().Includes(SideEffects::DependsOnGC())); DCHECK(access->GetSideEffects().Includes(SideEffects::DependsOnGC())); // TODO: Code generation for HArrayGet and HArraySet will check whether the input address // is an HIntermediateAddress and generate appropriate code. // We would like to replace the `HArrayGet` and `HArraySet` with custom instructions (maybe // `HArm64Load` and `HArm64Store`,`HArmLoad` and `HArmStore`). We defer these changes // because these new instructions would not bring any advantages yet. // Also see the comments in // `InstructionCodeGeneratorARMVIXL::VisitArrayGet()` // `InstructionCodeGeneratorARMVIXL::VisitArraySet()` // `InstructionCodeGeneratorARM64::VisitArrayGet()` // `InstructionCodeGeneratorARM64::VisitArraySet()`. return true; } bool TryExtractVecArrayAccessAddress(HVecMemoryOperation* access, HInstruction* index) { if (index->IsConstant()) { // If index is constant the whole address calculation often can be done by LDR/STR themselves. // TODO: Treat the case with not-embedable constant. return false; } HGraph* graph = access->GetBlock()->GetGraph(); ArenaAllocator* allocator = graph->GetAllocator(); DataType::Type packed_type = access->GetPackedType(); uint32_t data_offset = mirror::Array::DataOffset( DataType::Size(packed_type)).Uint32Value(); size_t component_shift = DataType::SizeShift(packed_type); bool is_extracting_beneficial = false; // It is beneficial to extract index intermediate address only if there are at least 2 users. for (const HUseListNode<HInstruction*>& use : index->GetUses()) { HInstruction* user = use.GetUser(); if (user->IsVecMemoryOperation() && user != access) { HVecMemoryOperation* another_access = user->AsVecMemoryOperation(); DataType::Type another_packed_type = another_access->GetPackedType(); uint32_t another_data_offset = mirror::Array::DataOffset( DataType::Size(another_packed_type)).Uint32Value(); size_t another_component_shift = DataType::SizeShift(another_packed_type); if (another_data_offset == data_offset && another_component_shift == component_shift) { is_extracting_beneficial = true; break; } } else if (user->IsIntermediateAddressIndex()) { HIntermediateAddressIndex* another_access = user->AsIntermediateAddressIndex(); uint32_t another_data_offset = another_access->GetOffset()->AsIntConstant()->GetValue(); size_t another_component_shift = another_access->GetShift()->AsIntConstant()->GetValue(); if (another_data_offset == data_offset && another_component_shift == component_shift) { is_extracting_beneficial = true; break; } } } if (!is_extracting_beneficial) { return false; } // Proceed to extract the index + data_offset address computation. HIntConstant* offset = graph->GetIntConstant(data_offset); HIntConstant* shift = graph->GetIntConstant(component_shift); HIntermediateAddressIndex* address = new (allocator) HIntermediateAddressIndex(index, offset, shift, kNoDexPc); access->GetBlock()->InsertInstructionBefore(address, access); access->ReplaceInput(address, 1); return true; } } // namespace art