about summary refs log tree commit diff
path: root/src/rustllvm/DemoteSimd.cpp
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2018-10-14 12:27:22 -0700
committerAlex Crichton <alex@alexcrichton.com>2018-10-19 02:35:00 -0700
commit3cc8f738d4247a9b475d8e074b621e602ac2b7be (patch)
treedba6d11f4bc2476e5e8bc3ed47d5986343651f06 /src/rustllvm/DemoteSimd.cpp
parentb1bdf04c97dd6631c627fe1a9d7fe2bb68e35dfd (diff)
downloadrust-3cc8f738d4247a9b475d8e074b621e602ac2b7be.tar.gz
rust-3cc8f738d4247a9b475d8e074b621e602ac2b7be.zip
rustc: Fix (again) simd vectors by-val in ABI
The issue of passing around SIMD types as values between functions has
seen [quite a lot] of [discussion], and although we thought [we fixed
it][quite a lot] it [wasn't]! This PR is a change to rustc to, again,
try to fix this issue.

The fundamental problem here remains the same, if a SIMD vector argument
is passed by-value in LLVM's function type, then if the caller and
callee disagree on target features a miscompile happens. We solve this
by never passing SIMD vectors by-value, but LLVM will still thwart us
with its argument promotion pass to promote by-ref SIMD arguments to
by-val SIMD arguments.

This commit is an attempt to thwart LLVM thwarting us. We, just before
codegen, will take yet another look at the LLVM module and demote any
by-value SIMD arguments we see. This is a very manual attempt by us to
ensure the codegen for a module keeps working, and it unfortunately is
likely producing suboptimal code, even in release mode. The saving grace
for this, in theory, is that if SIMD types are passed by-value across
a boundary in release mode it's pretty unlikely to be performance
sensitive (as it's already doing a load/store, and otherwise
perf-sensitive bits should be inlined).

The implementation here is basically a big wad of C++. It was largely
copied from LLVM's own argument promotion pass, only doing the reverse.
In local testing this...

Closes #50154
Closes #52636
Closes #54583
Closes #55059

[quite a lot]: https://github.com/rust-lang/rust/pull/47743
[discussion]: https://github.com/rust-lang/rust/issues/44367
[wasn't]: https://github.com/rust-lang/rust/issues/50154
Diffstat (limited to 'src/rustllvm/DemoteSimd.cpp')
-rw-r--r--src/rustllvm/DemoteSimd.cpp189
1 files changed, 189 insertions, 0 deletions
diff --git a/src/rustllvm/DemoteSimd.cpp b/src/rustllvm/DemoteSimd.cpp
new file mode 100644
index 00000000000..e9203baa0d7
--- /dev/null
+++ b/src/rustllvm/DemoteSimd.cpp
@@ -0,0 +1,189 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include <vector>
+#include <set>
+
+#include "rustllvm.h"
+
+#if LLVM_VERSION_GE(5, 0)
+
+#include "llvm/IR/CallSite.h"
+#include "llvm/IR/Module.h"
+#include "llvm/ADT/STLExtras.h"
+
+using namespace llvm;
+
+static std::vector<Function*>
+GetFunctionsWithSimdArgs(Module *M) {
+  std::vector<Function*> Ret;
+
+  for (auto &F : M->functions()) {
+    // Skip all intrinsic calls as these are always tightly controlled to "work
+    // correctly", so no need to fixup any of these.
+    if (F.isIntrinsic())
+      continue;
+
+    // We're only interested in rustc-defined functions, not unstably-defined
+    // imported SIMD ffi functions.
+    if (F.isDeclaration())
+      continue;
+
+    // Argument promotion only happens on internal functions, so skip demoting
+    // arguments in external functions like FFI shims and such.
+    if (!F.hasLocalLinkage())
+      continue;
+
+    // If any argument to this function is a by-value vector type, then that's
+    // bad! The compiler didn't generate any functions that looked like this,
+    // and we try to rely on LLVM to not do this! Argument promotion may,
+    // however, promote arguments from behind references. In any case, figure
+    // out if we're interested in demoting this argument.
+    if (any_of(F.args(), [](Argument &arg) { return arg.getType()->isVectorTy(); }))
+      Ret.push_back(&F);
+  }
+
+  return Ret;
+}
+
+extern "C" void
+LLVMRustDemoteSimdArguments(LLVMModuleRef Mod) {
+  Module *M = unwrap(Mod);
+
+  auto Functions = GetFunctionsWithSimdArgs(M);
+
+  for (auto F : Functions) {
+    // Build up our list of new parameters and new argument attributes.
+    // We're only changing those arguments which are vector types.
+    SmallVector<Type*, 8> Params;
+    SmallVector<AttributeSet, 8> ArgAttrVec;
+    auto PAL = F->getAttributes();
+    for (auto &Arg : F->args()) {
+      auto *Ty = Arg.getType();
+      if (Ty->isVectorTy()) {
+        Params.push_back(PointerType::get(Ty, 0));
+        ArgAttrVec.push_back(AttributeSet());
+      } else {
+        Params.push_back(Ty);
+        ArgAttrVec.push_back(PAL.getParamAttributes(Arg.getArgNo()));
+      }
+    }
+
+    // Replace `F` with a new function with our new signature. I'm... not really
+    // sure how this works, but this is all the steps `ArgumentPromotion` does
+    // to replace a signature as well.
+    assert(!F->isVarArg()); // ArgumentPromotion should skip these fns
+    FunctionType *NFTy = FunctionType::get(F->getReturnType(), Params, false);
+    Function *NF = Function::Create(NFTy, F->getLinkage(), F->getName());
+    NF->copyAttributesFrom(F);
+    NF->setSubprogram(F->getSubprogram());
+    F->setSubprogram(nullptr);
+    NF->setAttributes(AttributeList::get(F->getContext(),
+                                         PAL.getFnAttributes(),
+                                         PAL.getRetAttributes(),
+                                         ArgAttrVec));
+    ArgAttrVec.clear();
+    F->getParent()->getFunctionList().insert(F->getIterator(), NF);
+    NF->takeName(F);
+
+    // Iterate over all invocations of `F`, updating all `call` instructions to
+    // store immediate vector types in a local `alloc` instead of a by-value
+    // vector.
+    //
+    // Like before, much of this is copied from the `ArgumentPromotion` pass in
+    // LLVM.
+    SmallVector<Value*, 16> Args;
+    while (!F->use_empty()) {
+      CallSite CS(F->user_back());
+      assert(CS.getCalledFunction() == F);
+      Instruction *Call = CS.getInstruction();
+      const AttributeList &CallPAL = CS.getAttributes();
+
+      // Loop over the operands, inserting an `alloca` and a store for any
+      // argument we're demoting to be by reference
+      //
+      // FIXME: we probably want to figure out an LLVM pass to run and clean up
+      // this function and instructions we're generating, we should in theory
+      // only generate a maximum number of `alloca` instructions rather than
+      // one-per-variable unconditionally.
+      CallSite::arg_iterator AI = CS.arg_begin();
+      size_t ArgNo = 0;
+      for (Function::arg_iterator I = F->arg_begin(), E = F->arg_end(); I != E;
+           ++I, ++AI, ++ArgNo) {
+        if (I->getType()->isVectorTy()) {
+          AllocaInst *AllocA = new AllocaInst(I->getType(), 0, nullptr, "", Call);
+          new StoreInst(*AI, AllocA, Call);
+          Args.push_back(AllocA);
+          ArgAttrVec.push_back(AttributeSet());
+        } else {
+          Args.push_back(*AI);
+          ArgAttrVec.push_back(CallPAL.getParamAttributes(ArgNo));
+        }
+      }
+      assert(AI == CS.arg_end());
+
+      // Create a new call instructions which we'll use to replace the old call
+      // instruction, copying over as many attributes and such as possible.
+      SmallVector<OperandBundleDef, 1> OpBundles;
+      CS.getOperandBundlesAsDefs(OpBundles);
+
+      CallSite NewCS;
+      if (InvokeInst *II = dyn_cast<InvokeInst>(Call)) {
+        InvokeInst::Create(NF, II->getNormalDest(), II->getUnwindDest(),
+                           Args, OpBundles, "", Call);
+      } else {
+        auto *NewCall = CallInst::Create(NF, Args, OpBundles, "", Call);
+        NewCall->setTailCallKind(cast<CallInst>(Call)->getTailCallKind());
+        NewCS = NewCall;
+      }
+      NewCS.setCallingConv(CS.getCallingConv());
+      NewCS.setAttributes(
+          AttributeList::get(F->getContext(), CallPAL.getFnAttributes(),
+                             CallPAL.getRetAttributes(), ArgAttrVec));
+      NewCS->setDebugLoc(Call->getDebugLoc());
+      Args.clear();
+      ArgAttrVec.clear();
+      Call->replaceAllUsesWith(NewCS.getInstruction());
+      NewCS->takeName(Call);
+      Call->eraseFromParent();
+    }
+
+    // Splice the body of the old function right into the new function.
+    NF->getBasicBlockList().splice(NF->begin(), F->getBasicBlockList());
+
+    // Update our new function to replace all uses of the by-value argument with
+    // loads of the pointer argument we've generated.
+    //
+    // FIXME: we probably want to only generate one load instruction per
+    // function? Or maybe run an LLVM pass to clean up this function?
+    for (Function::arg_iterator I = F->arg_begin(),
+                                E = F->arg_end(),
+                                I2 = NF->arg_begin();
+         I != E;
+         ++I, ++I2) {
+      if (I->getType()->isVectorTy()) {
+        I->replaceAllUsesWith(new LoadInst(&*I2, "", &NF->begin()->front()));
+      } else {
+        I->replaceAllUsesWith(&*I2);
+      }
+      I2->takeName(&*I);
+    }
+
+    // Delete all references to the old function, it should be entirely dead
+    // now.
+    M->getFunctionList().remove(F);
+  }
+}
+
+#else // LLVM_VERSION_GE(8, 0)
+extern "C" void
+LLVMRustDemoteSimdArguments(LLVMModuleRef Mod) {
+}
+#endif // LLVM_VERSION_GE(8, 0)