Index: include/llvm/InitializePasses.h =================================================================== --- include/llvm/InitializePasses.h +++ include/llvm/InitializePasses.h @@ -168,6 +168,7 @@ void initializeHWAddressSanitizerLegacyPassPass(PassRegistry &); void initializeIPCPPass(PassRegistry&); void initializeIPSCCPLegacyPassPass(PassRegistry&); +void initializeIRCanonicalizerPass(PassRegistry&); void initializeIRCELegacyPassPass(PassRegistry&); void initializeIRTranslatorPass(PassRegistry&); void initializeIVUsersWrapperPassPass(PassRegistry&); Index: include/llvm/LinkAllPasses.h =================================================================== --- include/llvm/LinkAllPasses.h +++ include/llvm/LinkAllPasses.h @@ -54,6 +54,7 @@ #include "llvm/Transforms/Utils.h" #include "llvm/Transforms/Utils/SymbolRewriter.h" #include "llvm/Transforms/Utils/UnifyFunctionExitNodes.h" +#include "llvm/Transforms/Utils/IRCanonicalizer.h" #include "llvm/Transforms/Vectorize.h" #include @@ -117,6 +118,7 @@ (void) llvm::createLoopGuardWideningPass(); (void) llvm::createIPConstantPropagationPass(); (void) llvm::createIPSCCPPass(); + (void) llvm::createIRCanonicalizerPass(); (void) llvm::createInductiveRangeCheckEliminationPass(); (void) llvm::createIndVarSimplifyPass(); (void) llvm::createInstSimplifyLegacyPass(); Index: include/llvm/Transforms/Utils/IRCanonicalizer.h =================================================================== --- /dev/null +++ include/llvm/Transforms/Utils/IRCanonicalizer.h @@ -0,0 +1,93 @@ +//===---------------- IRCanonicalizer.h - IR Canonicalizer ------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// \file +/// This header declares the IRCanonicalizer class which aims to transform LLVM +/// Modules into a canonical form by reordering and renaming instructions while +/// preserving the same semantics. This tool makes it easier to spot semantic +/// differences while diffing two modules which have undergone different passes. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TRANSFORMS_UTILS_IRCANONICALIZER_H +#define LLVM_TRANSFORMS_UTILS_IRCANONICALIZER_H + +#include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Module.h" +#include "llvm/Pass.h" +#include "llvm/PassRegistry.h" +#include "llvm/Support/CommandLine.h" +#include + +using namespace llvm; + +namespace llvm { + +/// IRCanonicalizer aims to transform LLVM IR into canonical form. +class IRCanonicalizer : public ModulePass { +public: + static char ID; + + /// \name Canonicalizer flags. + /// @{ + /// Preserves original order of instructions. + static cl::opt PreserveOrder; + /// Renames all instructions (including user-named). + static cl::opt RenameAll; + /// Folds all regular instructions (including pre-outputs). + static cl::opt FoldPreoutputs; + /// @} + + /// Constructor for the IRCanonicalizer. + IRCanonicalizer() : ModulePass(ID) { + initializeIRCanonicalizerPass(*PassRegistry::getPassRegistry()); + } + + bool runOnModule(Module &module) override; + +private: + // Random constant for hashing, so the state isn't zero. + const uint64_t MagicHashConstant = 0x6acaa36bef8325c5ULL; + + /// \name Naming. + /// @{ + void nameFunctionArguments(Function &F); + void nameBasicBlocks(Function &F); + void nameInstruction(Instruction *I); + void nameAsInitialInstruction(Instruction *I); + void nameAsRegularInstruction(Instruction *I); + void foldInstructionName(Instruction *I); + /// @} + + /// \name Reordering instructions. + /// @{ + void reorderInstructions(SmallVector &Outputs); + void reorderInstruction(Instruction *Used, Instruction *User, + SmallPtrSet &Visited); + void reorderInstructionOperandsByNames(Instruction *I); + /// @} + + /// \name Utility methods. + /// @{ + SmallVector collectOutputInstructions(Function &F); + bool isOutput(const Instruction *I); + bool isInitialInstruction(const Instruction *I); + bool hasOnlyImmediateOperands(const Instruction *I); + SetVector + getOutputFootprint(Instruction *I, + SmallPtrSet &Visited); + /// @} +}; + +Pass *createIRCanonicalizerPass(); + +} // namespace llvm + +#endif // LLVM_TRANSFORMS_UTILS_IRCANONICALIZER_H \ No newline at end of file Index: lib/Transforms/Utils/CMakeLists.txt =================================================================== --- lib/Transforms/Utils/CMakeLists.txt +++ lib/Transforms/Utils/CMakeLists.txt @@ -24,6 +24,7 @@ ImportedFunctionsInliningStatistics.cpp InstructionNamer.cpp IntegerDivision.cpp + IRCanonicalizer.cpp LCSSA.cpp LibCallsShrinkWrap.cpp Local.cpp Index: lib/Transforms/Utils/IRCanonicalizer.cpp =================================================================== --- /dev/null +++ lib/Transforms/Utils/IRCanonicalizer.cpp @@ -0,0 +1,539 @@ +//===--------------- IRCanonicalizer.cpp - IR Canonicalizer ---------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// \file +/// This file implements the IRCanonicalizer class which aims to transform LLVM +/// Modules into a canonical form by reordering and renaming instructions while +/// preserving the same semantics. This tool makes it easier to spot semantic +/// differences while diffing two modules which have undergone different passes. +/// +//===----------------------------------------------------------------------===// + +#include "llvm/Transforms/Utils/IRCanonicalizer.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Module.h" +#include "llvm/Support/CommandLine.h" +#include +#include +#include + +using namespace llvm; + +#define DEBUG_TYPE "ir-canonicalizer" + +char IRCanonicalizer::ID = 0; + +cl::opt IRCanonicalizer::PreserveOrder( + "preserve-order", cl::Hidden, + cl::desc("Preserves original instruction order")); +cl::opt IRCanonicalizer::RenameAll( + "rename-all", cl::Hidden, + cl::desc("Renames all instructions (including user-named)")); +cl::opt IRCanonicalizer::FoldPreoutputs( + "fold-all", cl::Hidden, + cl::desc("Folds all regular instructions (including pre-outputs)")); + +INITIALIZE_PASS(IRCanonicalizer, "ir-canonicalizer", + "Transforms IR into canonical form", false, false) + +Pass *llvm::createIRCanonicalizerPass() { return new IRCanonicalizer(); } + +/// Entry method to the Canonicalizer. +/// +/// \param M Module to canonicalize. +bool IRCanonicalizer::runOnModule(Module &M) { + for (auto &F : M) { + nameFunctionArguments(F); + nameBasicBlocks(F); + + SmallVector Outputs = collectOutputInstructions(F); + + if (!PreserveOrder) + reorderInstructions(Outputs); + + for (auto &I : Outputs) + nameInstruction(I); + + for (auto &I : instructions(F)) { + if (!PreserveOrder && I.isCommutative()) + reorderInstructionOperandsByNames(&I); + + foldInstructionName(&I); + } + } + return true; +} + +/// Numbers arguments. +/// +/// \param F Function whose arguments will be renamed. +void IRCanonicalizer::nameFunctionArguments(Function &F) { + int ArgumentCounter = 0; + for (auto &A : F.args()) { + if (RenameAll || A.getName().empty()) { + A.setName("a" + Twine(ArgumentCounter)); + ++ArgumentCounter; + } + } +} + +/// Names basic blocks using a generated hash for each basic block in +/// a function considering the opcode and the order of output instructions. +/// +/// \param F Function containing basic blocks to rename. +void IRCanonicalizer::nameBasicBlocks(Function &F) { + for (auto &B : F) { + // Initialize to a magic constant, so the state isn't zero. + uint64_t Hash = MagicHashConstant; + + // Hash considering output instruction opcodes. + for (auto &I : B) { + if (isOutput(&I)) { + Hash = hashing::detail::hash_16_bytes(Hash, I.getOpcode()); + } + } + + if (RenameAll || B.getName().empty()) { + // Name basic block. Substring hash to make diffs more readable. + B.setName("bb" + std::to_string(Hash).substr(0, 5)); + } + } +} + +/// Names instructions graphically (recursive) in accordance with the +/// def-use tree, starting from the initial instructions (defs), finishing at +/// the output (top-most user) instructions (depth-first). +/// +/// \param I Instruction to be renamed. +void IRCanonicalizer::nameInstruction(Instruction *I) { + // Determine the type of instruction to name. + if (isInitialInstruction(I)) { + // This is an initial instruction. + nameAsInitialInstruction(I); + } else { + // This must be a regular instruction. + nameAsRegularInstruction(I); + } +} + +/// Names instruction following the scheme: +/// vl00000Callee(Operands) +/// +/// Where 00000 is a hash calculated considering instruction's opcode and output +/// footprint. Callee's name is only included when instruction's type is +/// CallInst. In cases where instruction is commutative, operands list is also +/// sorted. +/// +/// Renames instruction only when RenameAll flag is raised or instruction is +/// unnamed. +/// +/// \see getOutputFootprint() +/// \param I Instruction to be renamed. +void IRCanonicalizer::nameAsInitialInstruction(Instruction *I) { + if (I->getType()->isVoidTy() || (!I->getName().empty() && !RenameAll)) + return; + + // Instruction operands for further sorting. + SmallVector Operands; + + // Collect operands. + for (auto &OP : I->operands()) { + if (!isa(OP)) { + std::string TextRepresentation; + raw_string_ostream Stream(TextRepresentation); + OP->printAsOperand(Stream, false); + Operands.push_back(Stream.str()); + } + } + + if (I->isCommutative()) + llvm::sort(Operands); + + std::string OperandList = + std::accumulate(Operands.begin(), Operands.end(), std::string(), + [](const std::string &a, const std::string &b) { + return a + (a.length() > 0 ? ", " : "") + b; + }); + + // Initialize to a magic constant, so the state isn't zero. + uint64_t Hash = MagicHashConstant; + + // Consider instruction's opcode in the hash. + Hash = hashing::detail::hash_16_bytes(Hash, I->getOpcode()); + + SmallPtrSet Visited; + // Get output footprint for I. + SetVector outputFootprint = getOutputFootprint(I, Visited); + + // Consider output footprint in the hash. + for (auto output : outputFootprint) + Hash = hashing::detail::hash_16_bytes(Hash, output); + + // Base instruction name. + std::string Name = "vl" + std::to_string(Hash).substr(0, 5); + + // In case of CallInst, consider callee in the instruction name. + if (auto CI = dyn_cast(I)) { + Function *F = CI->getCalledFunction(); + + if (F != nullptr) { + Name.append(F->getName()); + } + } + + Name.append("(" + OperandList + ")"); + I->setName(Name); +} + +/// Names instruction following the scheme: +/// op00000Callee(Operands) +/// +/// Where 00000 is a hash calculated considering instruction's opcode, its +/// operands' opcodes and order. Callee's name is only included when +/// instruction's type is CallInst. In cases where instruction is commutative, +/// operand list is also sorted. +/// +/// Names instructions recursively in accordance with the def-use tree, +/// starting from the initial instructions (defs), finishing at +/// the output (top-most user) instructions (depth-first). +/// +/// Renames instruction only when RenameAll flag is raised or instruction is +/// unnamed. +/// +/// \see getOutputFootprint() +/// \param I Instruction to be renamed. +void IRCanonicalizer::nameAsRegularInstruction(Instruction *I) { + // Instruction operands for further sorting. + SmallVector Operands; + + // The name of a regular instruction depends + // on the names of its operands. Hence, all + // operands must be named first in the use-def + // walk. + + // Collect operands. + for (auto &OP : I->operands()) { + if (auto IOP = dyn_cast(OP)) { + // Walk down the use-def chain. + nameInstruction(IOP); + Operands.push_back(IOP->getName()); + } else if (isa(OP) && !isa(OP)) { + // This must be an immediate value. + std::string TextRepresentation; + raw_string_ostream Stream(TextRepresentation); + OP->printAsOperand(Stream, false); + Operands.push_back(Stream.str()); + } + } + + if (I->isCommutative()) + llvm::sort(Operands.begin(), Operands.end()); + + std::string OperandList = + std::accumulate(Operands.begin(), Operands.end(), std::string(), + [](const std::string &a, const std::string &b) { + return a + (a.length() > 0 ? ", " : "") + b; + }); + + // Initialize to a magic constant, so the state isn't zero. + uint64_t Hash = MagicHashConstant; + + // Consider instruction opcode in the hash. + Hash = hashing::detail::hash_16_bytes(Hash, I->getOpcode()); + + // Operand opcodes for further sorting (commutative). + SmallVector OperandsOpcodes; + + // Collect operand opcodes for hashing. + for (auto &OP : I->operands()) + if (auto IOP = dyn_cast(OP)) + OperandsOpcodes.push_back(IOP->getOpcode()); + + if (I->isCommutative()) + llvm::sort(OperandsOpcodes.begin(), OperandsOpcodes.end()); + + // Consider operand opcodes in the hash. + for (auto Code : OperandsOpcodes) + Hash = hashing::detail::hash_16_bytes(Hash, Code); + + // Base instruction name. + std::string Name = "op" + std::to_string(Hash).substr(0, 5); + + // In case of CallInst, consider callee in the instruction name. + if (const CallInst *CI = dyn_cast(I)) + if (const Function *F = CI->getCalledFunction()) + Name.append(F->getName()); + + Name.append("(" + OperandList + ")"); + + if ((I->getName().empty() || RenameAll) && !I->getType()->isVoidTy()) + I->setName(Name); +} + +/// Shortens instruction's name. This method removes called function name from +/// the instruction name and substitutes the call chain with a corresponding +/// list of operands. +/// +/// Examples: +/// op00000Callee(op00001Callee(...), vl00000Callee(1, 2), ...) -> +/// op00000(op00001, vl00000, ...) vl00000Callee(1, 2) -> vl00000(1, 2) +/// +/// This method omits output instructions and pre-output (instructions directly +/// used by an output instruction) instructions (by default). By default it also +/// does not affect user named instructions. +/// +/// \param I Instruction whose name will be folded. +void IRCanonicalizer::foldInstructionName(Instruction *I) { + // If this flag is raised, fold all regular + // instructions (including pre-outputs). + if (!FoldPreoutputs) { + // Don't fold if one of the users is an output instruction. + for (auto U : I->users()) + if (auto IU = dyn_cast(U)) + if (isOutput(IU)) + return; + } + + // Don't fold if it is an output instruction or has no op prefix. + if (isOutput(I) || I->getName().substr(0, 2) != "op") + return; + + // Instruction operands. + SmallVector Operands; + + for (auto &OP : I->operands()) { + if (const Instruction *IOP = dyn_cast(OP)) { + bool hasCanonicalName = I->getName().substr(0, 2) == "op" || + I->getName().substr(0, 2) == "vl"; + + Operands.push_back(hasCanonicalName ? IOP->getName().substr(0, 7) + : IOP->getName()); + } + } + + if (I->isCommutative()) + llvm::sort(Operands.begin(), Operands.end()); + + std::string OperandList = + std::accumulate(Operands.begin(), Operands.end(), std::string(), + [](const std::string &a, const std::string &b) { + return a + (a.length() > 0 ? ", " : "") + b; + }); + + std::string Name = I->getName().substr(0, 7); + Name.append("(" + OperandList + ")"); + I->setName(Name); +} + +/// Reorders instructions by walking up the tree from each operand of an output +/// instruction and reducing the def-use distance. +/// This method assumes that output instructions were collected top-down, +/// otherwise the def-use chain may be broken. +/// This method is a wrapper for recursive reorderInstruction(). +/// +/// \see reorderInstruction() +/// \param Outputs Vector of pointers to output instructions collected top-down. +void IRCanonicalizer::reorderInstructions( + SmallVector &Outputs) { + // This method assumes output instructions were collected top-down, + // otherwise the def-use chain may be broken. + + SmallPtrSet Visited; + + // Walk up the tree. + for (auto &I : Outputs) + for (auto &OP : I->operands()) + if (auto IOP = dyn_cast(OP)) + reorderInstruction(IOP, I, Visited); +} + +/// Reduces def-use distance or places instruction at the end of the basic +/// block. Continues to walk up the def-use tree recursively. Used by +/// reorderInstructions(). +/// +/// \see reorderInstructions() +/// \param Used Pointer to the instruction whose value is used by the \p User. +/// \param User Pointer to the instruction which uses the \p Used. +/// \param Visited Set of visited instructions. +void IRCanonicalizer::reorderInstruction( + Instruction *Used, Instruction *User, + SmallPtrSet &Visited) { + + if (!Visited.count(Used)) { + Visited.insert(Used); + + if (Used->getParent() == User->getParent()) { + // If Used and User share the same basic block move Used just before User. + Used->moveBefore(User); + } else { + // Otherwise move Used to the very end of its basic block. + Used->moveBefore(&Used->getParent()->back()); + } + + for (auto &OP : Used->operands()) { + if (auto IOP = dyn_cast(OP)) { + // Walk up the def-use tree. + reorderInstruction(IOP, Used, Visited); + } + } + } +} + +/// Reorders instruction's operands alphabetically. This method assumes +/// that passed instruction is commutative. Changing the operand order +/// in other instructions may change the semantics. +/// +/// \param I Instruction whose operands will be reordered. +void IRCanonicalizer::reorderInstructionOperandsByNames(Instruction *I) { + // This method assumes that passed I is commutative, + // changing the order of operands in other instructions + // may change the semantics. + + // Instruction operands for further sorting. + SmallVector, 4> Operands; + + // Collect operands. + for (auto &OP : I->operands()) { + if (auto VOP = dyn_cast(OP)) { + if (isa(VOP)) { + // This is an an instruction. + Operands.push_back( + std::pair(VOP->getName(), VOP)); + } else { + std::string TextRepresentation; + raw_string_ostream Stream(TextRepresentation); + OP->printAsOperand(Stream, false); + Operands.push_back(std::pair(Stream.str(), VOP)); + } + } + } + + // Sort operands. + llvm::sort(Operands.begin(), Operands.end(), + [](const std::pair &lhs, + const std::pair &rhs) { + return lhs.first < rhs.first; + }); + + // Reorder operands. + unsigned position = 0; + for (auto &OP : I->operands()) { + OP.set(Operands[position].second); + position++; + } +} + +/// Returns a vector of output instructions. An output is an instruction which +/// has side-effects or is ReturnInst. Uses isOutput(). +/// +/// \see isOutput() +/// \param F Function to collect outputs from. +SmallVector +IRCanonicalizer::collectOutputInstructions(Function &F) { + // Output instructions are collected top-down in each function, + // any change may break the def-use chain in reordering methods. + SmallVector Outputs; + + for (auto &I : instructions(F)) + if (isOutput(&I)) + Outputs.push_back(&I); + + return Outputs; +} + +/// Helper method checking whether the instruction may have side effects or is +/// ReturnInst. +/// +/// \param I Considered instruction. +bool IRCanonicalizer::isOutput(const Instruction *I) { + // Outputs are such instructions which may have side effects or is ReturnInst. + if (I->mayHaveSideEffects() || isa(I)) + return true; + + return false; +} + +/// Helper method checking whether the instruction has users and only +/// immediate operands. +/// +/// \param I Considered instruction. +bool IRCanonicalizer::isInitialInstruction(const Instruction *I) { + // Initial instructions are such instructions whose values are used by + // other instructions, yet they only depend on immediate values. + return std::distance(I->user_begin(), I->user_end()) != 0 && + hasOnlyImmediateOperands(I); +} + +/// Helper method checking whether the instruction has only immediate operands. +/// +/// \param I Considered instruction. +bool IRCanonicalizer::hasOnlyImmediateOperands(const Instruction *I) { + for (auto &OP : I->operands()) { + if (isa(OP)) { + // Found non-immediate operand (instruction). + return false; + } + } + + return true; +} + +/// Helper method returning indices (distance from the begining of the basic +/// block) of outputs using the \p I (eliminates repetitions). Walks down the +/// def-use tree recursively. +/// +/// \param I Considered instruction. +/// \param Visited Set of visited instructions. +SetVector IRCanonicalizer::getOutputFootprint( + Instruction *I, SmallPtrSet &Visited) { + + // Vector containing indexes of outputs (no repetitions), + // which use I in the order of walking down the def-use tree. + SetVector Outputs; + + if (!Visited.count(I)) { + Visited.insert(I); + + if (isOutput(I)) { + // Gets output instruction's parent function. + Function *Func = I->getParent()->getParent(); + + // Finds and inserts the index of the output to the vector. + unsigned count = 0; + for (auto &B : *Func) { + for (auto &E : B) { + if (&E == I) { + Outputs.insert(count); + } + count++; + } + } + + // Returns to the used instruction. + return Outputs; + } + + for (auto U : I->users()) { + if (auto UI = dyn_cast(U)) { + // Vector for outputs which use UI. + SetVector OutputsUsingUI = getOutputFootprint(UI, Visited); + + // Insert the indexes of outputs using UI. + Outputs.insert(OutputsUsingUI.begin(), OutputsUsingUI.end()); + } + } + } + + // Return to the used instruction. + return Outputs; +} \ No newline at end of file Index: lib/Transforms/Utils/Utils.cpp =================================================================== --- lib/Transforms/Utils/Utils.cpp +++ lib/Transforms/Utils/Utils.cpp @@ -27,6 +27,7 @@ initializeBreakCriticalEdgesPass(Registry); initializeCanonicalizeAliasesLegacyPassPass(Registry); initializeInstNamerPass(Registry); + initializeIRCanonicalizerPass(Registry); initializeLCSSAWrapperPassPass(Registry); initializeLibCallsShrinkWrapLegacyPassPass(Registry); initializeLoopSimplifyPass(Registry); Index: test/Transforms/IRCanonicalizer/naming-arguments.ll =================================================================== --- /dev/null +++ test/Transforms/IRCanonicalizer/naming-arguments.ll @@ -0,0 +1,7 @@ +; RUN: opt -S --ir-canonicalizer < %s | FileCheck %s + +; CHECK: @foo(i32 %a0, i32 %a1) +define i32 @foo(i32, i32) { + %tmp = mul i32 %0, %1 + ret i32 %tmp +} Index: test/Transforms/IRCanonicalizer/naming-basic-blocks.ll =================================================================== --- /dev/null +++ test/Transforms/IRCanonicalizer/naming-basic-blocks.ll @@ -0,0 +1,8 @@ +; RUN: opt -S --ir-canonicalizer --rename-all < %s | FileCheck %s + +define i32 @foo(i32 %a0) { +; CHECK: bb{{([0-9]{5})}} +entry: + %a = add i32 %a0, 2 + ret i32 %a +} \ No newline at end of file Index: test/Transforms/IRCanonicalizer/naming-instructions.ll =================================================================== --- /dev/null +++ test/Transforms/IRCanonicalizer/naming-instructions.ll @@ -0,0 +1,12 @@ +; RUN: opt -S --ir-canonicalizer --rename-all < %s | FileCheck %s + +define i32 @foo(i32 %a0) { +entry: +; CHECK: %"vl{{([0-9]{5})}}(%a0, 2)" + %a = add i32 %a0, 2 +; CHECK: %"op{{([0-9]{5})}}(vl{{([0-9]{5})}})" + %b = add i32 %a, 6 +; CHECK: %"op{{([0-9]{5})}}(8, op{{([0-9]{5})}}(6, vl{{([0-9]{5})}}(%a0, 2)))" + %c = add i32 %b, 8 + ret i32 %c +} \ No newline at end of file Index: test/Transforms/IRCanonicalizer/reordering-instructions.ll =================================================================== --- /dev/null +++ test/Transforms/IRCanonicalizer/reordering-instructions.ll @@ -0,0 +1,14 @@ +; RUN: opt -S --ir-canonicalizer < %s | FileCheck %s + +define double @foo(double %a0, double %a1) { +entry: +; CHECK: %a +; CHECK: %c +; CHECK: %b +; CHECK: %d + %a = fmul double %a0, %a1 + %b = fmul double %a0, 2.000000e+00 + %c = fmul double %a, 6.000000e+00 + %d = fmul double %b, 6.000000e+00 + ret double %d +} \ No newline at end of file