Index: include/llvm/CodeGen/CommandFlags.h =================================================================== --- include/llvm/CodeGen/CommandFlags.h +++ include/llvm/CodeGen/CommandFlags.h @@ -184,6 +184,11 @@ cl::desc("Use .init_array instead of .ctors."), cl::init(false)); +cl::opt +NOPInsertion("nop-insertion", + cl::desc("Randomly add noop instructions to create fine-grained diversity."), + cl::init(false)); + cl::opt StopAfter("stop-after", cl::desc("Stop compilation after a specific pass"), cl::value_desc("pass-name"), @@ -238,6 +243,7 @@ Options.StackAlignmentOverride = OverrideStackAlignment; Options.TrapFuncName = TrapFuncName; Options.PositionIndependentExecutable = EnablePIE; + Options.NOPInsertion = NOPInsertion; Options.UseInitArray = UseInitArray; Options.DataSections = DataSections; Options.FunctionSections = FunctionSections; Index: include/llvm/CodeGen/NOPInsertion.h =================================================================== --- /dev/null +++ include/llvm/CodeGen/NOPInsertion.h @@ -0,0 +1,35 @@ +//===-- NOPInsertion.h - NOP Insertion -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This pass adds fine-grained diversity by displacing code using randomly +// placed (optionally target supplied) NOP instructions. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CODEGEN_NOPINSERTION_H +#define LLVM_CODEGEN_NOPINSERTION_H + +#include "llvm/CodeGen/MachineFunctionPass.h" + +namespace llvm { + +class NOPInsertion : public MachineFunctionPass { +public: + static char ID; + + NOPInsertion(); + + bool runOnMachineFunction(MachineFunction &MF); + + void getAnalysisUsage(AnalysisUsage &AU) const override; +}; + +} + +#endif // LLVM_CODEGEN_NOPINSERTION_H Index: include/llvm/CodeGen/Passes.h =================================================================== --- include/llvm/CodeGen/Passes.h +++ include/llvm/CodeGen/Passes.h @@ -588,6 +588,10 @@ /// the intrinsic for later emission to the StackMap. extern char &StackMapLivenessID; + /// NOPInsertion - This pass adds fine-grained diversity by displacing code + /// using randomly placed (optionally target supplied) NOP instructions. + extern char &NOPInsertionID; + /// createJumpInstrTables - This pass creates jump-instruction tables. ModulePass *createJumpInstrTablesPass(); } // End llvm namespace Index: include/llvm/IR/Module.h =================================================================== --- include/llvm/IR/Module.h +++ include/llvm/IR/Module.h @@ -206,8 +206,6 @@ std::string ModuleID; ///< Human readable identifier for the module std::string TargetTriple; ///< Platform target triple Module compiled on void *NamedMDSymTab; ///< NamedMDNode names. - // Allow lazy initialization in const method. - mutable RandomNumberGenerator *RNG; ///< The random number generator for this module. // We need to keep the string because the C API expects us to own the string // representation. @@ -256,10 +254,16 @@ /// @returns a string containing the module-scope inline assembly blocks. const std::string &getModuleInlineAsm() const { return GlobalScopeAsm; } - /// Get the RandomNumberGenerator for this module. The RNG can be - /// seeded via -rng-seed= and is salted with the ModuleID. - /// The returned RNG should not be shared across threads. - RandomNumberGenerator &getRNG() const; + /// Get a RandomNumberGenerator salted for use with this module. The + /// RNG can be seeded via -rng-seed= and is salted with the + /// ModuleID and the provided pass salt. The returned RNG should not + /// be shared across threads or passes. + /// + /// A unique RNG per pass ensures a reproducible random stream even + /// when other randomness consuming passes are added or removed. In + /// addition, the random stream will be reproducible across LLVM + /// versions when the pass does not change. + RandomNumberGenerator *createRNG(const Pass* P) const; /// @} /// @name Module Level Mutators Index: include/llvm/InitializePasses.h =================================================================== --- include/llvm/InitializePasses.h +++ include/llvm/InitializePasses.h @@ -199,6 +199,7 @@ void initializeMergeFunctionsPass(PassRegistry&); void initializeModuleDebugInfoPrinterPass(PassRegistry&); void initializeNoAAPass(PassRegistry&); +void initializeNOPInsertionPass(PassRegistry&); void initializeObjCARCAliasAnalysisPass(PassRegistry&); void initializeObjCARCAPElimPass(PassRegistry&); void initializeObjCARCExpandPass(PassRegistry&); Index: include/llvm/Support/RandomNumberGenerator.h =================================================================== --- include/llvm/Support/RandomNumberGenerator.h +++ include/llvm/Support/RandomNumberGenerator.h @@ -7,9 +7,9 @@ // //===----------------------------------------------------------------------===// // -// This file defines an abstraction for random number generation (RNG). -// Note that the current implementation is not cryptographically secure -// as it uses the C++11 facilities. +// This file defines an abstraction for deterministic random number +// generation (RNG). Note that the current implementation is not +// cryptographically secure as it uses the C++11 facilities. // //===----------------------------------------------------------------------===// @@ -24,26 +24,27 @@ namespace llvm { /// A random number generator. -/// Instances of this class should not be shared across threads. +/// +/// Instances of this class should not be shared across threads. The +/// seed should be set by passing the -rng-seed= option. Use +/// Module::createRNG to create a new RNG instance for use with that +/// module. class RandomNumberGenerator { public: - /// Seeds and salts the underlying RNG engine. The salt of type StringRef - /// is passed into the constructor. The seed can be set on the command - /// line via -rng-seed=. - /// The reason for the salt is to ensure different random streams even if - /// the same seed is used for multiple invocations of the compiler. - /// A good salt value should add additional entropy and be constant across - /// different machines (i.e., no paths) to allow for reproducible builds. - /// An instance of this class can be retrieved from the current Module. - /// \see Module::getRNG - RandomNumberGenerator(StringRef Salt); - /// Returns a random number in the range [0, Max). - uint64_t next(uint64_t Max); + uint_fast64_t operator()(); private: + /// Seeds and salts the underlying RNG engine. + /// + /// This constructor should not be used directly. Instead use + /// Module::createRNG to create a new RNG salted with the Module ID. + RandomNumberGenerator(StringRef Salt); + // 64-bit Mersenne Twister by Matsumoto and Nishimura, 2000 // http://en.cppreference.com/w/cpp/numeric/random/mersenne_twister_engine + // This RNG is deterministically portable across C++11 + // implementations. std::mt19937_64 Generator; // Noncopyable. @@ -51,6 +52,8 @@ LLVM_DELETED_FUNCTION; RandomNumberGenerator & operator=(const RandomNumberGenerator &other) LLVM_DELETED_FUNCTION; + + friend class Module; }; } Index: include/llvm/Target/TargetInstrInfo.h =================================================================== --- include/llvm/Target/TargetInstrInfo.h +++ include/llvm/Target/TargetInstrInfo.h @@ -30,6 +30,7 @@ class MCInst; class MCSchedModel; class MCSymbolRefExpr; +class RandomNumberGenerator; class SDNode; class ScheduleHazardRecognizer; class SelectionDAG; @@ -676,6 +677,15 @@ virtual void insertNoop(MachineBasicBlock &MBB, MachineBasicBlock::iterator MI) const; + /// insertNoop - Insert a type of noop into the instruction stream at the + /// specified point to introduce fine-grained diversity. A target may randomly + /// choose from a pool of valid noops using the provided RNG. + virtual void insertNoop(MachineBasicBlock &MBB, + MachineBasicBlock::iterator MI, + RandomNumberGenerator *RNG) const { + insertNoop(MBB, MI); + } + /// getNoopForMachoTarget - Return the noop instruction to use for a noop. virtual void getNoopForMachoTarget(MCInst &NopInst) const { Index: include/llvm/Target/TargetOptions.h =================================================================== --- include/llvm/Target/TargetOptions.h +++ include/llvm/Target/TargetOptions.h @@ -61,7 +61,7 @@ JITEmitDebugInfoToDisk(false), GuaranteedTailCallOpt(false), DisableTailCalls(false), StackAlignmentOverride(0), EnableFastISel(false), PositionIndependentExecutable(false), - UseInitArray(false), DisableIntegratedAS(false), + NOPInsertion(false), UseInitArray(false), DisableIntegratedAS(false), CompressDebugSections(false), FunctionSections(false), DataSections(false), TrapUnreachable(false), TrapFuncName(""), FloatABIType(FloatABI::Default), @@ -165,6 +165,9 @@ /// if the relocation model is anything other than PIC. unsigned PositionIndependentExecutable : 1; + /// Randomly insert noop instructions to create fine-grained diversity + unsigned NOPInsertion : 1; + /// UseInitArray - Use .init_array instead of .ctors for static /// constructors. unsigned UseInitArray : 1; Index: lib/CodeGen/CMakeLists.txt =================================================================== --- lib/CodeGen/CMakeLists.txt +++ lib/CodeGen/CMakeLists.txt @@ -69,6 +69,7 @@ MachineSink.cpp MachineTraceMetrics.cpp MachineVerifier.cpp + NOPInsertion.cpp OcamlGC.cpp OptimizePHIs.cpp PHIElimination.cpp Index: lib/CodeGen/CodeGen.cpp =================================================================== --- lib/CodeGen/CodeGen.cpp +++ lib/CodeGen/CodeGen.cpp @@ -73,6 +73,7 @@ initializeLowerIntrinsicsPass(Registry); initializeMachineFunctionPrinterPassPass(Registry); initializeStackMapLivenessPass(Registry); + initializeNOPInsertionPass(Registry); } void LLVMInitializeCodeGen(LLVMPassRegistryRef R) { Index: lib/CodeGen/NOPInsertion.cpp =================================================================== --- /dev/null +++ lib/CodeGen/NOPInsertion.cpp @@ -0,0 +1,101 @@ +//===- NOPInsertion.cpp - NOP Insertion -----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This pass adds fine-grained diversity by displacing code using randomly +// placed (optionally target supplied) NOP instructions. +// +//===----------------------------------------------------------------------===// + +#include "llvm/CodeGen/NOPInsertion.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/CodeGen/MachineInstrBuilder.h" +#include "llvm/CodeGen/MachineModuleInfo.h" +#include "llvm/CodeGen/MachineRegisterInfo.h" +#include "llvm/CodeGen/Passes.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Module.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/RandomNumberGenerator.h" +#include "llvm/Target/TargetInstrInfo.h" +using namespace llvm; + +#define DEBUG_TYPE "nop-insertion" + +static cl::opt +NOPInsertionPercentage( + "nop-insertion-percentage", + cl::desc("Percentage of instructions that have NOPs prepended"), + cl::init(50)); + +static cl::opt +MaxNOPsPerInstruction( + "max-nops-per-instruction", + llvm::cl::desc("Maximum number of NOPs per instruction"), + llvm::cl::init(1)); + + +STATISTIC(InsertedNOPs, + "Total number of noop type instructions inserted for diversity"); + +char NOPInsertion::ID = 0; +char &llvm::NOPInsertionID = NOPInsertion::ID; +INITIALIZE_PASS(NOPInsertion, "nop-insertion", + "NOP Insertion for fine-grained code randomization", false, + false) + +NOPInsertion::NOPInsertion() : MachineFunctionPass(ID) { + initializeNOPInsertionPass(*PassRegistry::getPassRegistry()); + + // clamp percentage to 100 + if (NOPInsertionPercentage > 100) + NOPInsertionPercentage = 100; +} + +void NOPInsertion::getAnalysisUsage(AnalysisUsage &AU) const { + AU.setPreservesCFG(); + MachineFunctionPass::getAnalysisUsage(AU); +} + +bool NOPInsertion::runOnMachineFunction(MachineFunction &Fn) { + const TargetInstrInfo *TII = Fn.getTarget().getInstrInfo(); + + RandomNumberGenerator *RNG = Fn.getFunction()->getParent()->createRNG(this); + for (MachineFunction::iterator BB = Fn.begin(), E = Fn.end(); BB != E; ++BB) { + MachineBasicBlock::iterator FirstTerm = BB->getFirstTerminator(); + // Insert NOPs before instruction. + for (MachineBasicBlock::iterator I = BB->begin(); I != BB->end(); ) { + MachineBasicBlock::iterator NextI = std::next(I); + if (I->isPseudo()) { + I = NextI; + continue; + } + + // Insert random number of NOP-like instructions. + for (unsigned i = 0; i < MaxNOPsPerInstruction; i++) { + unsigned Roll = (*RNG)() % 100; // FIXME: not uniform + if (Roll >= NOPInsertionPercentage) + continue; + + TII->insertNoop(*BB, I, RNG); + + ++InsertedNOPs; + } + + // Do not insert NOPs between terminators. + if (I == FirstTerm) + break; + + I = NextI; + } + } + return true; +} + Index: lib/CodeGen/Passes.cpp =================================================================== --- lib/CodeGen/Passes.cpp +++ lib/CodeGen/Passes.cpp @@ -554,6 +554,9 @@ addPass(createGCInfoPrinter(dbgs())); } + if (TM->Options.NOPInsertion) + addPass(&NOPInsertionID); + // Basic block placement. if (getOptLevel() != CodeGenOpt::None) addBlockPlacement(); Index: lib/IR/Module.cpp =================================================================== --- lib/IR/Module.cpp +++ lib/IR/Module.cpp @@ -46,7 +46,7 @@ // Module::Module(StringRef MID, LLVMContext &C) - : Context(C), Materializer(), ModuleID(MID), RNG(nullptr), DL("") { + : Context(C), Materializer(), ModuleID(MID), DL("") { ValSymTab = new ValueSymbolTable(); NamedMDSymTab = new StringMap(); Context.addModule(this); @@ -61,9 +61,27 @@ NamedMDList.clear(); delete ValSymTab; delete static_cast *>(NamedMDSymTab); - delete RNG; } +RandomNumberGenerator *Module::createRNG(const Pass* P) const { + SmallString<32> Salt(P->getPassName()); + + // This RNG is guaranteed to produce the same random stream only + // when the Module ID and thus the input filename is the same. This + // might be problematic if the input filename extension changes + // (e.g. from .c to .bc or .ll). + // + // We could store this salt in NamedMetadata, but this would make + // the parameter non-const. This would unfortunately make this + // interface unusable by any Machine passes, since they only have a + // const reference to their IR Module. Alternatively we can always + // store salt metadata from the Module constructor. + Salt += sys::path::filename(getModuleIdentifier()); + + return new RandomNumberGenerator(Salt); +} + + /// getNamedValue - Return the first global value in the module with /// the specified name, of arbitrary type. This method returns null /// if a global with the specified name is not found. @@ -358,16 +376,6 @@ return &DL; } -// We want reproducible builds, but ModuleID may be a full path so we just use -// the filename to salt the RNG (although it is not guaranteed to be unique). -RandomNumberGenerator &Module::getRNG() const { - if (RNG == nullptr) { - StringRef Salt = sys::path::filename(ModuleID); - RNG = new RandomNumberGenerator(Salt); - } - return *RNG; -} - //===----------------------------------------------------------------------===// // Methods to control the materialization of GlobalValues in the Module. // Index: lib/Support/RandomNumberGenerator.cpp =================================================================== --- lib/Support/RandomNumberGenerator.cpp +++ lib/Support/RandomNumberGenerator.cpp @@ -7,16 +7,16 @@ // //===----------------------------------------------------------------------===// // -// This file implements random number generation (RNG). +// This file implements deterministic random number generation (RNG). // The current implementation is NOT cryptographically secure as it uses // the C++11 facilities. // //===----------------------------------------------------------------------===// #define DEBUG_TYPE "rng" -#include "llvm/Support/RandomNumberGenerator.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" +#include "llvm/Support/RandomNumberGenerator.h" using namespace llvm; @@ -31,31 +31,25 @@ RandomNumberGenerator::RandomNumberGenerator(StringRef Salt) { DEBUG( if (Seed == 0) - errs() << "Warning! Using unseeded random number generator.\n" + dbgs() << "Warning! Using unseeded random number generator.\n" ); - // Combine seed and salt using std::seed_seq. - // Entropy: Seed-low, Seed-high, Salt... + // Combine seed and salts using std::seed_seq. + // Data: Seed-low, Seed-high, Salt + // Note: std::seed_seq can only store 32-bit values, even though we + // are using a 64-bit RNG. This isn't a problem since the Mersenne + // twister constructor copies these correctly into its initial state. std::vector Data; - Data.reserve(2 + Salt.size()/4 + 1); + Data.reserve(2 + Salt.size()); Data.push_back(Seed); Data.push_back(Seed >> 32); - uint32_t Pack = 0; - for (size_t I = 0; I < Salt.size(); ++I) { - Pack <<= 8; - Pack += Salt[I]; - - if (I%4 == 3) - Data.push_back(Pack); - } - Data.push_back(Pack); + std::copy(Salt.begin(), Salt.end(), Data.end()); std::seed_seq SeedSeq(Data.begin(), Data.end()); Generator.seed(SeedSeq); } -uint64_t RandomNumberGenerator::next(uint64_t Max) { - std::uniform_int_distribution distribution(0, Max - 1); - return distribution(Generator); +uint_fast64_t RandomNumberGenerator::operator()() { + return Generator(); } Index: lib/Target/X86/X86InstrInfo.h =================================================================== --- lib/Target/X86/X86InstrInfo.h +++ lib/Target/X86/X86InstrInfo.h @@ -360,6 +360,13 @@ bool shouldScheduleAdjacent(MachineInstr* First, MachineInstr *Second) const override; + void insertNoop(MachineBasicBlock &MBB, + MachineBasicBlock::iterator MI) const override; + + void insertNoop(MachineBasicBlock &MBB, + MachineBasicBlock::iterator MI, + RandomNumberGenerator *RNG) const override; + void getNoopForMachoTarget(MCInst &NopInst) const override; bool Index: lib/Target/X86/X86InstrInfo.cpp =================================================================== --- lib/Target/X86/X86InstrInfo.cpp +++ lib/Target/X86/X86InstrInfo.cpp @@ -34,6 +34,7 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Support/RandomNumberGenerator.h" #include "llvm/Target/TargetOptions.h" #include @@ -5293,6 +5294,56 @@ MI->setDesc(get(table[Domain-1])); } +/// insertNoop - Insert a noop into the instruction stream at the specified +/// point. +void X86InstrInfo::insertNoop(MachineBasicBlock &MBB, + MachineBasicBlock::iterator MI) const { + DebugLoc DL; + BuildMI(MBB, MI, DL, get(X86::NOOP)); +} + +/// insertNoop - Insert a randomly chosen type of noop into the instruction +/// stream at the specified point to introduce fine-grained diversity. +void X86InstrInfo::insertNoop(MachineBasicBlock &MBB, + MachineBasicBlock::iterator MI, + RandomNumberGenerator *RNG) const { + enum { NOP, + MOV_BP, MOV_SP, + LEA_SI, LEA_DI, + MAX_NOPS }; + + static const unsigned NopRegs[MAX_NOPS][2] = { + { 0, 0 }, + { X86::EBP, X86::RBP }, + { X86::ESP, X86::RSP }, + { X86::ESI, X86::RSI }, + { X86::EDI, X86::RDI }, + }; + + unsigned Type = (*RNG)() % MAX_NOPS; + + DebugLoc DL; + bool is64Bit = Subtarget.is64Bit(); + unsigned Reg = NopRegs[Type][is64Bit]; + + switch (Type) { + case NOP: + BuildMI(MBB, MI, DL, get(X86::NOOP)); + break; + case MOV_BP: + case MOV_SP: + copyPhysReg(MBB, MI, DL, Reg, Reg, false); + break; + case LEA_SI: + case LEA_DI: { + unsigned opc = is64Bit ? X86::LEA64r : X86::LEA32r; + addRegOffset(BuildMI(MBB, MI, DL, get(opc), Reg), + Reg, false, 0); + break; + } + } +} + /// getNoopForMachoTarget - Return the noop instruction to use for a noop. void X86InstrInfo::getNoopForMachoTarget(MCInst &NopInst) const { NopInst.setOpcode(X86::NOOP); Index: test/CodeGen/X86/nop-insert-percentage.ll =================================================================== --- /dev/null +++ test/CodeGen/X86/nop-insert-percentage.ll @@ -0,0 +1,47 @@ +; RUN: llc < %s -rng-seed=5 -nop-insertion -nop-insertion-percentage=10 \ +; RUN: | FileCheck %s --check-prefix=PERCENT10 +; RUN: llc < %s -rng-seed=5 -nop-insertion -nop-insertion-percentage=50 \ +; RUN: | FileCheck %s --check-prefix=PERCENT50 +; RUN: llc < %s -rng-seed=5 -nop-insertion -nop-insertion-percentage=100 \ +; RUN: | FileCheck %s --check-prefix=PERCENT100 + +; This test case tests NOP insertion at varying percentage levels. + +define i32 @test(i32 %x, i32 %y, i32 %z) { +entry: + %t1 = add i32 %x, %y + %t2 = mul i32 %t1, %z + %t3 = add i32 %t2, %x + %t4 = mul i32 %t3, %z + %t5 = add i32 %t4, %x + %t6 = mul i32 %t5, %z + %t7 = add i32 %t6, %x + %t8 = mul i32 %t7, %z + %t9 = add i32 %t8, %x + %t10 = mul i32 %t9, %z + %t11 = add i32 %t10, %x + ret i32 %t11 +} + +; PERCENT10: leaq (%rsi), %rsi + +; PERCENT50: movq %rbp, %rbp +; PERCENT50: leaq (%rsi), %rsi +; PERCENT50: movq %rbp, %rbp +; PERCENT50: leaq (%rdi), %rdi +; PERCENT50: movq %rsp, %rsp +; PERCENT50: leaq (%rsi), %rsi +; PERCENT50: leaq (%rdi), %rdi + +; PERCENT100: leaq (%rdi), %rdi +; PERCENT100: movq %rbp, %rbp +; PERCENT100: leaq (%rsi), %rsi +; PERCENT100: movq %rbp, %rbp +; PERCENT100: leaq (%rdi), %rdi +; PERCENT100: movq %rsp, %rsp +; PERCENT100: leaq (%rsi), %rsi +; PERCENT100: leaq (%rsi), %rsi +; PERCENT100: nop +; PERCENT100: leaq (%rsi), %rsi +; PERCENT100: movq %rsp, %rsp +; PERCENT100: movq %rbp, %rbp Index: test/CodeGen/X86/nop-insert.ll =================================================================== --- /dev/null +++ test/CodeGen/X86/nop-insert.ll @@ -0,0 +1,44 @@ +; RUN: llc < %s -nop-insertion | FileCheck %s +; RUN: llc < %s -nop-insertion -salt-data="test" -rng-seed=1 | FileCheck %s --check-prefix=SEED1 +; RUN: llc < %s -nop-insertion -salt-data="test" -rng-seed=25 | FileCheck %s --check-prefix=SEED2 +; RUN: llc < %s -nop-insertion -salt-data="test" -rng-seed=1534 | FileCheck %s --check-prefix=SEED3 +; RUN: llc < %s -nop-insertion -salt-data="different entropy" -rng-seed=1 | FileCheck %s --check-prefix=SALT + +; This test case checks that NOPs are inserted, and that the RNG seed +; affects both the placement (position of imull) and choice of these NOPs. + +; CHECK: movq %rsp, %rsp +; CHECK: imull +; CHECK: movq %rbp, %rbp +; CHECK-NOT: leaq +; CHECK-NOT: nop + +; SEED1: imull +; SEED1: leaq (%rsi), %rsi +; SEED1-NOT: movq +; SEED1-NOT: nop + +; SEED2: movq %rbp, %rbp +; SEED2: imull +; SEED2: movq %rbp, %rbp +; SEED2-NOT: leaq +; SEED2-NOT: nop + +; SEED3: movq %rsp, %rsp +; SEED3: imull +; SEED3: movq %rsp, %rsp +; SEED3-NOT: leaq +; SEED3-NOT: nop + +; SALT: imull +; SALT: nop +; SALT: nop +; SALT-NOT: leaq +; SALT-NOT: movq + +define i32 @test1(i32 %x, i32 %y, i32 %z) { +entry: + %tmp = mul i32 %x, %y + %tmp2 = add i32 %tmp, %z + ret i32 %tmp2 +}