================
@@ -0,0 +1,319 @@
+//===- CIRABIRewriteContext.cpp - CIR ABI rewrite context ----------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "CIRABIRewriteContext.h"
+#include "mlir/IR/Builders.h"
+#include "clang/CIR/Dialect/IR/CIRAttrs.h"
+#include "clang/CIR/Dialect/IR/CIRTypes.h"
+
+using namespace cir;
+using namespace mlir;
+using namespace mlir::abi;
+
+// This rewrite context currently supports only the Direct (no coercion) and
+// Ignore classifications.  All other ArgKinds emit an errorNYI here rather
+// than silently passing through, because the IR they would produce is wrong
+// (e.g. Expand should flatten an aggregate into multiple primitives, not
+// pass it through as a single value).  Subsequent PRs in the
+// CallConvLowering split series add the remaining kinds and the
+// signature-shaping behavior that goes with them (sret / byval insert
+// extra arguments, struct coercion replaces one argument with several).
+
+namespace {
+
+bool needsRewrite(const FunctionClassification &fc) {
+  if ((fc.returnInfo.kind != ArgKind::Direct) || fc.returnInfo.coercedType)
+    return true;
+  for (const ArgClassification &ac : fc.argInfos)
+    if ((ac.kind != ArgKind::Direct) || ac.coercedType)
+      return true;
+  return false;
+}
+
+SmallVector<unsigned> ignoredArgIndices(const FunctionClassification &fc) {
+  SmallVector<unsigned> v;
+  for (auto [idx, ac] : llvm::enumerate(fc.argInfos))
+    if (ac.kind == ArgKind::Ignore)
+      v.push_back(idx);
+  return v;
+}
+
+/// Build the new argument-type list for a function whose ABI classification
+/// is \p fc.  This currently handles only Direct (no coercion) and Ignore;
+/// other kinds emit an error.  Classifications that add arguments (e.g.
+/// Indirect-sret would prepend a return-pointer arg) are not yet
+/// implemented and will arrive in a subsequent PR.
+LogicalResult buildNewArgTypes(ArrayRef<Type> oldArgTypes,
+                               const FunctionClassification &fc,
+                               SmallVectorImpl<Type> &newArgTypes,
+                               function_ref<InFlightDiagnostic()> emitError) {
+  assert(newArgTypes.empty() && "expected an empty output vector");
+  newArgTypes.reserve(oldArgTypes.size());
+  for (auto [idx, ac] : llvm::enumerate(fc.argInfos)) {
+    Type origTy = oldArgTypes[idx];
+    switch (ac.kind) {
+    case ArgKind::Direct:
+      if (ac.coercedType) {
+        emitError() << "Direct with coerced type at arg " << idx
+                    << " not yet implemented in CallConvLowering";
+        return failure();
+      }
+      newArgTypes.push_back(origTy);
+      break;
+    case ArgKind::Ignore:
+      break;
+    case ArgKind::Expand:
+      emitError() << "Expand at arg " << idx
+                  << " not yet implemented in CallConvLowering";
+      return failure();
+    case ArgKind::Extend:
+      emitError() << "Extend at arg " << idx
+                  << " not yet implemented in CallConvLowering";
+      return failure();
+    case ArgKind::Indirect:
+      emitError() << "Indirect at arg " << idx
+                  << " not yet implemented in CallConvLowering";
+      return failure();
+    }
+  }
+  return success();
+}
+
+/// Compute the new return type for a function whose return classification
+/// is \p retInfo.  As with `buildNewArgTypes`, only Direct (no coercion)
+/// and Ignore are implemented here; the remaining kinds emit an error.
+Type computeNewReturnType(Type origRetTy, const ArgClassification &retInfo,
+                          MLIRContext *ctx,
+                          function_ref<InFlightDiagnostic()> emitError) {
+  switch (retInfo.kind) {
+  case ArgKind::Direct:
+    if (retInfo.coercedType) {
+      emitError() << "Direct return with coerced type not yet implemented "
+                  << "in CallConvLowering";
+      return nullptr;
+    }
+    return origRetTy;
+  case ArgKind::Ignore:
+    return cir::VoidType::get(ctx);
+  case ArgKind::Expand:
+    emitError() << "Expand return is not allowed (classic codegen rejects "
+                << "it in EmitFunctionEpilog)";
+    return nullptr;
+  case ArgKind::Extend:
+    emitError() << "Extend return not yet implemented in CallConvLowering";
+    return nullptr;
+  case ArgKind::Indirect:
+    emitError() << "Indirect return (sret) not yet implemented in "
+                << "CallConvLowering";
+    return nullptr;
+  }
+  llvm_unreachable("all ArgKind cases handled");
+}
+
+/// Create a typed poison constant to stand in for a value the body of a
+/// function (or the result of a call) still references but whose ABI
+/// classification is Ignore.  Using poison is honest -- the value is
+/// genuinely unused at the ABI boundary -- and avoids a fake alloca+load
+/// pattern that would suggest we have a value when we don't.
+Value createIgnoredValue(OpBuilder &builder, Location loc, Type ty) {
+  return cir::ConstantOp::create(builder, loc, ty, cir::PoisonAttr::get(ty));
+}
+
+} // namespace
+
+LogicalResult CIRABIRewriteContext::rewriteFunctionDefinition(
+    FunctionOpInterface funcOpInterface, const FunctionClassification &fc,
+    OpBuilder &rewriter) {
+  // The pass driver (CallConvLoweringPass) only ever hands us cir.func ops,
+  // and the body of this routine is end-to-end CIR (it creates cir.constant,
+  // cir.return, etc.).  Cast once at the top so the rest of the function
+  // reads in CIR's own vocabulary, and so we can dispatch to the
+  // CIRGlobalValueInterface for isDefinition() (FunctionOpInterface alone
+  // does not inherit from CIRGlobalValueInterface).
+  cir::FuncOp funcOp = cast<cir::FuncOp>(funcOpInterface);
+
+  if (!needsRewrite(fc))
+    return success();
+
+  ArrayRef<Type> oldArgTypes = funcOp.getArgumentTypes();
+  ArrayRef<Type> oldResultTypes = funcOp.getResultTypes();
+  MLIRContext *ctx = funcOp->getContext();
+
+  // CIR follows LLVM IR's single-result rule: a function returns either
+  // zero or one value.  Document the invariant so a future multi-result
+  // change forces us to revisit the return-handling below.
+  assert(oldResultTypes.size() <= 1 &&
+         "CIR functions return zero or one value");
+
+  SmallVector<Type> newArgTypes;
+  if (failed(buildNewArgTypes(oldArgTypes, fc, newArgTypes,
+                              [&]() { return funcOp.emitOpError(); })))
+    return failure();
+
+  Type voidTy = cir::VoidType::get(ctx);
+  Type origRetTy = oldResultTypes.empty() ? voidTy : oldResultTypes[0];
+  Type newRetTy = computeNewReturnType(origRetTy, fc.returnInfo, ctx,
+                                       [&]() { return funcOp.emitOpError(); });
+  if (!newRetTy)
+    return failure();
+  SmallVector<Type> newResultTypes = {newRetTy};
+
+  if (funcOp.isDefinition()) {
+    Region &body = funcOp->getRegion(0);
+    if (!body.empty()) {
+      Block &entry = body.front();
+
+      // For each Ignored argument: drop the block argument and, if the
+      // body still references it, replace those uses with a poison
+      // constant.  Ignore classifications mean the value is empty / not
+      // passed at the ABI level, so any remaining uses are vacuous;
+      // poison says exactly that.
+      SmallVector<unsigned> ignored = ignoredArgIndices(fc);
+      for (unsigned blockIdx : llvm::reverse(ignored)) {
+        if (blockIdx >= entry.getNumArguments())
+          continue;
+        BlockArgument arg = entry.getArgument(blockIdx);
+        if (!arg.use_empty()) {
+          rewriter.setInsertionPointToStart(&entry);
+          Value poison =
+              createIgnoredValue(rewriter, funcOp.getLoc(), arg.getType());
+          arg.replaceAllUsesWith(poison);
+        }
+        entry.eraseArgument(blockIdx);
+      }
+    }
+
+    // When the return is classified Ignore but the original function had
+    // a non-void return type, every cir.return becomes a naked return.
+    // This relies on the invariant that computeNewReturnType has set
+    // newRetTy = void for Ignore above, and that the function type is
+    // updated below to match.  Asserting this keeps the dependency
+    // explicit.
+    if (fc.returnInfo.kind == ArgKind::Ignore && !oldResultTypes.empty()) {
+      assert(isa<cir::VoidType>(newRetTy) &&
+             "Ignore-return path requires the new return type to be void");
+      SmallVector<cir::ReturnOp> returns;
+      funcOp.walk([&](cir::ReturnOp r) { returns.push_back(r); });
+      for (cir::ReturnOp r : returns) {
+        if (r.getNumOperands() == 0)
+          continue;
+        rewriter.setInsertionPoint(r);
+        cir::ReturnOp::create(rewriter, r.getLoc());
+        r.erase();
+      }
+    }
+  }
+
+  Type newFnTy = funcOp.cloneTypeWith(newArgTypes, newResultTypes);
+  funcOp.setFunctionTypeAttr(TypeAttr::get(newFnTy));
+
+  // Keep the arg_attrs array in sync with the new argument count by
+  // dropping entries for every Ignored argument.  Without this the
+  // attribute array would have stale entries that no longer match any
+  // block argument.
+  SmallVector<unsigned> ignored = ignoredArgIndices(fc);
+  if (!ignored.empty())
+    if (auto existing = funcOp->getAttrOfType<ArrayAttr>("arg_attrs")) {
+      SmallVector<Attribute> kept;
+      kept.reserve(newArgTypes.size());
+      for (auto [oldIdx, attr] : llvm::enumerate(existing.getValue()))
+        if (oldIdx >= fc.argInfos.size() ||
+            fc.argInfos[oldIdx].kind != ArgKind::Ignore)
+          kept.push_back(attr);
+      funcOp->setAttr("arg_attrs", ArrayAttr::get(ctx, kept));
+    }
+
+  return success();
+}
+
+LogicalResult CIRABIRewriteContext::rewriteCallSite(
+    Operation *callOp, const FunctionClassification &fc, OpBuilder &rewriter) {
+  if (!needsRewrite(fc))
+    return success();
+
+  // TODO: also handle cir::TryCallOp (the throwing-call form used by C++
+  // exceptions) -- templating this routine on the call op type lets the
+  // rewriting logic stay shared.  Will land with the EH-aware follow-up
+  // since the EH paths also need their own ABI considerations.
+  auto call = cast<cir::CallOp>(callOp);
----------------
andykaylor wrote:

Can you detect indirect calls here and emit an error?

https://github.com/llvm/llvm-project/pull/195737
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to