diff options
author | 2016-01-11 13:43:31 +0000 | |
---|---|---|
committer | 2016-02-25 16:26:13 +0000 | |
commit | 9ff0d205fd60cba6753a91f613b198ca2d67f04d (patch) | |
tree | 86689672064d66d2c473045f934f948211ba0389 | |
parent | 950d063395c7cecbbe372fd607468018d661a35c (diff) |
Optimizing: ARM64 negated bitwise operations simplification
Use negated instructions on ARM64 to replace [bitwise operation + not]
patterns, that is:
a & ~b (BIC)
a | ~b (ORN)
a ^ ~b (EON)
The simplification only happens if the Not is only used by the bitwise
operation. It does not happen if both inputs are Not's (this should be
handled by a generic simplification applying De Morgan's laws).
Change-Id: I0e112b23fd8b8e10f09bfeff5994508a8ff96e9c
-rw-r--r-- | compiler/optimizing/code_generator_arm64.cc | 30 | ||||
-rw-r--r-- | compiler/optimizing/graph_visualizer.cc | 4 | ||||
-rw-r--r-- | compiler/optimizing/instruction_simplifier_arm64.cc | 55 | ||||
-rw-r--r-- | compiler/optimizing/instruction_simplifier_arm64.h | 7 | ||||
-rw-r--r-- | compiler/optimizing/nodes.h | 1 | ||||
-rw-r--r-- | compiler/optimizing/nodes_arm64.h | 60 | ||||
-rw-r--r-- | test/564-checker-negbitwise/expected.txt | 0 | ||||
-rw-r--r-- | test/564-checker-negbitwise/info.txt | 1 | ||||
-rw-r--r-- | test/564-checker-negbitwise/src/Main.java | 207 |
9 files changed, 365 insertions, 0 deletions
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index beb75f0afc..25487d2fad 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -1862,6 +1862,36 @@ void InstructionCodeGeneratorARM64::VisitAnd(HAnd* instruction) { HandleBinaryOp(instruction); } +void LocationsBuilderARM64::VisitArm64BitwiseNegatedRight(HArm64BitwiseNegatedRight* instr) { + DCHECK(Primitive::IsIntegralType(instr->GetType())) << instr->GetType(); + LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instr); + locations->SetInAt(0, Location::RequiresRegister()); + // There is no immediate variant of negated bitwise instructions in AArch64. + locations->SetInAt(1, Location::RequiresRegister()); + locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap); +} + +void InstructionCodeGeneratorARM64::VisitArm64BitwiseNegatedRight( + HArm64BitwiseNegatedRight* instr) { + Register dst = OutputRegister(instr); + Register lhs = InputRegisterAt(instr, 0); + Register rhs = InputRegisterAt(instr, 1); + + switch (instr->GetOpKind()) { + case HInstruction::kAnd: + __ Bic(dst, lhs, rhs); + break; + case HInstruction::kOr: + __ Orn(dst, lhs, rhs); + break; + case HInstruction::kXor: + __ Eon(dst, lhs, rhs); + break; + default: + LOG(FATAL) << "Unreachable"; + } +} + void LocationsBuilderARM64::VisitArm64DataProcWithShifterOp( HArm64DataProcWithShifterOp* instruction) { DCHECK(instruction->GetType() == Primitive::kPrimInt || diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc index a3d6bcf450..b9638f2027 100644 --- a/compiler/optimizing/graph_visualizer.cc +++ b/compiler/optimizing/graph_visualizer.cc @@ -443,6 +443,10 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { #endif #ifdef ART_ENABLE_CODEGEN_arm64 + void VisitArm64BitwiseNegatedRight(HArm64BitwiseNegatedRight* instruction) OVERRIDE { + StartAttributeStream("kind") << instruction->GetOpKind(); + } + void VisitArm64DataProcWithShifterOp(HArm64DataProcWithShifterOp* instruction) OVERRIDE { StartAttributeStream("kind") << instruction->GetInstrKind() << "+" << instruction->GetOpKind(); if (HArm64DataProcWithShifterOp::IsShiftOp(instruction->GetOpKind())) { diff --git a/compiler/optimizing/instruction_simplifier_arm64.cc b/compiler/optimizing/instruction_simplifier_arm64.cc index 83126a5c4d..c2bbdccc29 100644 --- a/compiler/optimizing/instruction_simplifier_arm64.cc +++ b/compiler/optimizing/instruction_simplifier_arm64.cc @@ -180,6 +180,53 @@ bool InstructionSimplifierArm64Visitor::TryMergeIntoUsersShifterOperand(HInstruc return true; } +bool InstructionSimplifierArm64Visitor::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(); + + HArm64BitwiseNegatedRight* neg_op = new (GetGraph()->GetArena()) + HArm64BitwiseNegatedRight(op->GetType(), op->GetKind(), hother, src, op->GetDexPc()); + + op->GetBlock()->ReplaceAndRemoveInstructionWith(op, neg_op); + hnot->GetBlock()->RemoveInstruction(hnot); + RecordSimplification(); + return true; + } + } + + return false; +} + +void InstructionSimplifierArm64Visitor::VisitAnd(HAnd* instruction) { + TryMergeNegatedInput(instruction); +} + void InstructionSimplifierArm64Visitor::VisitArrayGet(HArrayGet* instruction) { TryExtractArrayAccessAddress(instruction, instruction->GetArray(), @@ -200,6 +247,10 @@ void InstructionSimplifierArm64Visitor::VisitMul(HMul* instruction) { } } +void InstructionSimplifierArm64Visitor::VisitOr(HOr* instruction) { + TryMergeNegatedInput(instruction); +} + void InstructionSimplifierArm64Visitor::VisitShl(HShl* instruction) { if (instruction->InputAt(1)->IsConstant()) { TryMergeIntoUsersShifterOperand(instruction); @@ -232,5 +283,9 @@ void InstructionSimplifierArm64Visitor::VisitUShr(HUShr* instruction) { } } +void InstructionSimplifierArm64Visitor::VisitXor(HXor* instruction) { + TryMergeNegatedInput(instruction); +} + } // namespace arm64 } // namespace art diff --git a/compiler/optimizing/instruction_simplifier_arm64.h b/compiler/optimizing/instruction_simplifier_arm64.h index 37a34c0373..cf8458713f 100644 --- a/compiler/optimizing/instruction_simplifier_arm64.h +++ b/compiler/optimizing/instruction_simplifier_arm64.h @@ -51,14 +51,21 @@ class InstructionSimplifierArm64Visitor : public HGraphVisitor { return TryMergeIntoShifterOperand(use, bitfield_op, true); } + // For bitwise operations (And/Or/Xor) with a negated input, try to use + // a negated bitwise instruction. + bool TryMergeNegatedInput(HBinaryOperation* op); + // HInstruction visitors, sorted alphabetically. + void VisitAnd(HAnd* instruction) OVERRIDE; void VisitArrayGet(HArrayGet* instruction) OVERRIDE; void VisitArraySet(HArraySet* instruction) OVERRIDE; void VisitMul(HMul* instruction) OVERRIDE; + void VisitOr(HOr* instruction) OVERRIDE; void VisitShl(HShl* instruction) OVERRIDE; void VisitShr(HShr* instruction) OVERRIDE; void VisitTypeConversion(HTypeConversion* instruction) OVERRIDE; void VisitUShr(HUShr* instruction) OVERRIDE; + void VisitXor(HXor* instruction) OVERRIDE; OptimizingCompilerStats* stats_; }; diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 4185b2f2c5..c4764ccbb4 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -1268,6 +1268,7 @@ class HLoopInformationOutwardIterator : public ValueObject { #define FOR_EACH_CONCRETE_INSTRUCTION_ARM64(M) #else #define FOR_EACH_CONCRETE_INSTRUCTION_ARM64(M) \ + M(Arm64BitwiseNegatedRight, Instruction) \ M(Arm64DataProcWithShifterOp, Instruction) \ M(Arm64IntermediateAddress, Instruction) #endif diff --git a/compiler/optimizing/nodes_arm64.h b/compiler/optimizing/nodes_arm64.h index 173852a55d..75a71e78b8 100644 --- a/compiler/optimizing/nodes_arm64.h +++ b/compiler/optimizing/nodes_arm64.h @@ -118,6 +118,66 @@ class HArm64IntermediateAddress : public HExpression<2> { DISALLOW_COPY_AND_ASSIGN(HArm64IntermediateAddress); }; +class HArm64BitwiseNegatedRight : public HBinaryOperation { + public: + HArm64BitwiseNegatedRight(Primitive::Type result_type, + InstructionKind op, + HInstruction* left, + HInstruction* right, + uint32_t dex_pc = kNoDexPc) + : HBinaryOperation(result_type, left, right, SideEffects::None(), dex_pc), + op_kind_(op) { + DCHECK(op == HInstruction::kAnd || op == HInstruction::kOr || op == HInstruction::kXor) << op; + } + + template <typename T, typename U> + auto Compute(T x, U y) const -> decltype(x & ~y) { + static_assert(std::is_same<decltype(x & ~y), decltype(x | ~y)>::value && + std::is_same<decltype(x & ~y), decltype(x ^ ~y)>::value, + "Inconsistent negated bitwise types"); + switch (op_kind_) { + case HInstruction::kAnd: + return x & ~y; + case HInstruction::kOr: + return x | ~y; + case HInstruction::kXor: + return x ^ ~y; + default: + LOG(FATAL) << "Unreachable"; + UNREACHABLE(); + } + } + + HConstant* Evaluate(HIntConstant* x, HIntConstant* y) const OVERRIDE { + return GetBlock()->GetGraph()->GetIntConstant( + Compute(x->GetValue(), y->GetValue()), GetDexPc()); + } + HConstant* Evaluate(HLongConstant* x, HLongConstant* y) const OVERRIDE { + return GetBlock()->GetGraph()->GetLongConstant( + Compute(x->GetValue(), y->GetValue()), GetDexPc()); + } + HConstant* Evaluate(HFloatConstant* x ATTRIBUTE_UNUSED, + HFloatConstant* y ATTRIBUTE_UNUSED) const OVERRIDE { + LOG(FATAL) << DebugName() << " is not defined for float values"; + UNREACHABLE(); + } + HConstant* Evaluate(HDoubleConstant* x ATTRIBUTE_UNUSED, + HDoubleConstant* y ATTRIBUTE_UNUSED) const OVERRIDE { + LOG(FATAL) << DebugName() << " is not defined for double values"; + UNREACHABLE(); + } + + InstructionKind GetOpKind() const { return op_kind_; } + + DECLARE_INSTRUCTION(Arm64BitwiseNegatedRight); + + private: + // Specifies the bitwise operation, which will be then negated. + const InstructionKind op_kind_; + + DISALLOW_COPY_AND_ASSIGN(HArm64BitwiseNegatedRight); +}; + } // namespace art #endif // ART_COMPILER_OPTIMIZING_NODES_ARM64_H_ diff --git a/test/564-checker-negbitwise/expected.txt b/test/564-checker-negbitwise/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/564-checker-negbitwise/expected.txt diff --git a/test/564-checker-negbitwise/info.txt b/test/564-checker-negbitwise/info.txt new file mode 100644 index 0000000000..28b9e9e832 --- /dev/null +++ b/test/564-checker-negbitwise/info.txt @@ -0,0 +1 @@ +Test negated bitwise operations simplification on ARM64. diff --git a/test/564-checker-negbitwise/src/Main.java b/test/564-checker-negbitwise/src/Main.java new file mode 100644 index 0000000000..3de7be7161 --- /dev/null +++ b/test/564-checker-negbitwise/src/Main.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + + // A dummy value to defeat inlining of these routines. + static boolean doThrow = false; + + public static void assertIntEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + public static void assertLongEquals(long expected, long result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + /** + * Test merging of `NOT+AND` into `BIC`. + */ + + /// CHECK-START-ARM64: int Main.$opt$noinline$notAnd(int, int) instruction_simplifier_arm64 (before) + /// CHECK: <<Base:i\d+>> ParameterValue + /// CHECK: <<Mask:i\d+>> ParameterValue + /// CHECK: <<Not:i\d+>> Not [<<Mask>>] + /// CHECK: <<Op:i\d+>> And [<<Base>>,<<Not>>] + /// CHECK: Return [<<Op>>] + + /// CHECK-START-ARM64: int Main.$opt$noinline$notAnd(int, int) instruction_simplifier_arm64 (after) + /// CHECK: <<Base:i\d+>> ParameterValue + /// CHECK: <<Mask:i\d+>> ParameterValue + /// CHECK: <<NegOp:i\d+>> Arm64BitwiseNegatedRight [<<Base>>,<<Mask>>] kind:And + /// CHECK: Return [<<NegOp>>] + + /// CHECK-START-ARM64: int Main.$opt$noinline$notAnd(int, int) instruction_simplifier_arm64 (after) + /// CHECK-NOT: Not + /// CHECK-NOT: And + + /// CHECK-START-ARM64: int Main.$opt$noinline$notAnd(int, int) disassembly (after) + /// CHECK: bic w{{\d+}}, w{{\d+}}, w{{\d+}} + + public static int $opt$noinline$notAnd(int base, int mask) { + if (doThrow) throw new Error(); + return base & ~mask; + } + + /** + * Test merging of `NOT+ORR` into `ORN`. + */ + + /// CHECK-START-ARM64: long Main.$opt$noinline$notOr(long, long) instruction_simplifier_arm64 (before) + /// CHECK: <<Base:j\d+>> ParameterValue + /// CHECK: <<Mask:j\d+>> ParameterValue + /// CHECK: <<Not:j\d+>> Not [<<Mask>>] + /// CHECK: <<Op:j\d+>> Or [<<Base>>,<<Not>>] + /// CHECK: Return [<<Op>>] + + /// CHECK-START-ARM64: long Main.$opt$noinline$notOr(long, long) instruction_simplifier_arm64 (after) + /// CHECK: <<Base:j\d+>> ParameterValue + /// CHECK: <<Mask:j\d+>> ParameterValue + /// CHECK: <<NegOp:j\d+>> Arm64BitwiseNegatedRight [<<Base>>,<<Mask>>] kind:Or + /// CHECK: Return [<<NegOp>>] + + /// CHECK-START-ARM64: long Main.$opt$noinline$notOr(long, long) instruction_simplifier_arm64 (after) + /// CHECK-NOT: Not + /// CHECK-NOT: Or + + /// CHECK-START-ARM64: long Main.$opt$noinline$notOr(long, long) disassembly (after) + /// CHECK: orn x{{\d+}}, x{{\d+}}, x{{\d+}} + + public static long $opt$noinline$notOr(long base, long mask) { + if (doThrow) throw new Error(); + return base | ~mask; + } + + /** + * Test merging of `NOT+EOR` into `EON`. + */ + + /// CHECK-START-ARM64: int Main.$opt$noinline$notXor(int, int) instruction_simplifier_arm64 (before) + /// CHECK: <<Base:i\d+>> ParameterValue + /// CHECK: <<Mask:i\d+>> ParameterValue + /// CHECK: <<Not:i\d+>> Not [<<Mask>>] + /// CHECK: <<Op:i\d+>> Xor [<<Base>>,<<Not>>] + /// CHECK: Return [<<Op>>] + + /// CHECK-START-ARM64: int Main.$opt$noinline$notXor(int, int) instruction_simplifier_arm64 (after) + /// CHECK: <<Base:i\d+>> ParameterValue + /// CHECK: <<Mask:i\d+>> ParameterValue + /// CHECK: <<NegOp:i\d+>> Arm64BitwiseNegatedRight [<<Base>>,<<Mask>>] kind:Xor + /// CHECK: Return [<<NegOp>>] + + /// CHECK-START-ARM64: int Main.$opt$noinline$notXor(int, int) instruction_simplifier_arm64 (after) + /// CHECK-NOT: Not + /// CHECK-NOT: Xor + + /// CHECK-START-ARM64: int Main.$opt$noinline$notXor(int, int) disassembly (after) + /// CHECK: eon w{{\d+}}, w{{\d+}}, w{{\d+}} + + public static int $opt$noinline$notXor(int base, int mask) { + if (doThrow) throw new Error(); + return base ^ ~mask; + } + + /** + * Check that the transformation is also done when the base is a constant. + */ + + /// CHECK-START-ARM64: int Main.$opt$noinline$notXorConstant(int) instruction_simplifier_arm64 (before) + /// CHECK: <<Mask:i\d+>> ParameterValue + /// CHECK: <<Constant:i\d+>> IntConstant + /// CHECK: <<Not:i\d+>> Not [<<Mask>>] + /// CHECK: <<Op:i\d+>> Xor [<<Not>>,<<Constant>>] + /// CHECK: Return [<<Op>>] + + /// CHECK-START-ARM64: int Main.$opt$noinline$notXorConstant(int) instruction_simplifier_arm64 (after) + /// CHECK: <<Mask:i\d+>> ParameterValue + /// CHECK: <<Constant:i\d+>> IntConstant + /// CHECK: <<NegOp:i\d+>> Arm64BitwiseNegatedRight [<<Constant>>,<<Mask>>] kind:Xor + /// CHECK: Return [<<NegOp>>] + + /// CHECK-START-ARM64: int Main.$opt$noinline$notXorConstant(int) instruction_simplifier_arm64 (after) + /// CHECK-NOT: Not + /// CHECK-NOT: Xor + + /// CHECK-START-ARM64: int Main.$opt$noinline$notXorConstant(int) disassembly (after) + /// CHECK: mov <<Reg:w\d+>>, #0xf + /// CHECK: eon w{{\d+}}, <<Reg>>, w{{\d+}} + + public static int $opt$noinline$notXorConstant(int mask) { + if (doThrow) throw new Error(); + return 0xf ^ ~mask; + } + + /** + * Check that no transformation is done when Not has multiple uses. + */ + + /// CHECK-START-ARM64: int Main.$opt$noinline$notAndMultipleUses(int, int) instruction_simplifier_arm64 (before) + /// CHECK: <<Base:i\d+>> ParameterValue + /// CHECK: <<Mask:i\d+>> ParameterValue + /// CHECK: <<One:i\d+>> IntConstant + /// CHECK: <<Not:i\d+>> Not [<<Mask>>] + /// CHECK: <<Op1:i\d+>> And [<<Not>>,<<One>>] + /// CHECK: <<Op2:i\d+>> And [<<Base>>,<<Not>>] + /// CHECK: <<Add:i\d+>> Add [<<Op1>>,<<Op2>>] + /// CHECK: Return [<<Add>>] + + /// CHECK-START-ARM64: int Main.$opt$noinline$notAndMultipleUses(int, int) instruction_simplifier_arm64 (after) + /// CHECK: <<Base:i\d+>> ParameterValue + /// CHECK: <<Mask:i\d+>> ParameterValue + /// CHECK: <<One:i\d+>> IntConstant + /// CHECK: <<Not:i\d+>> Not [<<Mask>>] + /// CHECK: <<Op1:i\d+>> And [<<Not>>,<<One>>] + /// CHECK: <<Op2:i\d+>> And [<<Base>>,<<Not>>] + /// CHECK: <<Add:i\d+>> Add [<<Op1>>,<<Op2>>] + /// CHECK: Return [<<Add>>] + + /// CHECK-START-ARM64: int Main.$opt$noinline$notAndMultipleUses(int, int) instruction_simplifier_arm64 (after) + /// CHECK-NOT: Arm64BitwiseNegatedRight + + public static int $opt$noinline$notAndMultipleUses(int base, int mask) { + if (doThrow) throw new Error(); + int tmp = ~mask; + return (tmp & 0x1) + (base & tmp); + } + + /** + * Check that no transformation is done when both inputs are Not's. + */ + + // We don't check the instructions before the pass, since if De Morgan's laws + // have been applied then Not/Not/Or is replaced by And/Not. + + /// CHECK-START-ARM64: int Main.$opt$noinline$deMorganOr(int, int) instruction_simplifier_arm64 (after) + /// CHECK-NOT: Arm64BitwiseNegatedRight + + public static int $opt$noinline$deMorganOr(int a, int b) { + if (doThrow) throw new Error(); + return ~a | ~b; + } + + public static void main(String[] args) { + assertIntEquals(0xe, $opt$noinline$notAnd(0xf, 0x1)); + assertLongEquals(~0x0, $opt$noinline$notOr(0xf, 0x1)); + assertIntEquals(~0xe, $opt$noinline$notXor(0xf, 0x1)); + assertIntEquals(~0xe, $opt$noinline$notXorConstant(0x1)); + assertIntEquals(0xe, $opt$noinline$notAndMultipleUses(0xf, 0x1)); + assertIntEquals(~0x1, $opt$noinline$deMorganOr(0x3, 0x1)); + } +} |