CIRCT  20.0.0git
CalyxNative.cpp
Go to the documentation of this file.
1 //===- CalyxNative.cpp - Invoke the native Calyx compiler
2 //----------------------------===//
3 //
4 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5 // See https://llvm.org/LICENSE.txt for license information.
6 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 
8 //===----------------------------------------------------------------------===//
9 //
10 // Calls out to the native, Rust-based Calyx compiler using the `calyx` binary
11 // to run passes.
12 //
13 //===----------------------------------------------------------------------===//
14 
15 #include "mlir/Pass/Pass.h"
16 
19 #include "mlir/Parser/Parser.h"
20 #include "mlir/Support/FileUtilities.h"
21 #include "llvm/Support/MemoryBuffer.h"
22 #include "llvm/Support/Path.h"
23 #include "llvm/Support/Process.h"
24 #include "llvm/Support/ToolOutputFile.h"
25 
26 namespace circt {
27 #define GEN_PASS_DEF_CALYXNATIVE
28 #include "circt/Conversion/Passes.h.inc"
29 } // namespace circt
30 
31 using namespace mlir;
32 using namespace circt;
33 
34 /// ConversionPatterns.
35 
36 /// Pass entrypoint.
37 
38 namespace {
39 class CalyxNativePass : public circt::impl::CalyxNativeBase<CalyxNativePass> {
40 public:
41  void runOnOperation() override;
42 
43 private:
44  LogicalResult runOnModule(ModuleOp root);
45 };
46 } // end anonymous namespace
47 
48 void CalyxNativePass::runOnOperation() {
49  ModuleOp mod = getOperation();
50  if (failed(runOnModule(mod)))
51  return signalPassFailure();
52 }
53 
54 LogicalResult CalyxNativePass::runOnModule(ModuleOp root) {
55  SmallString<32> execName = llvm::sys::path::filename("calyx");
56  llvm::ErrorOr<std::string> exeMb = llvm::sys::findProgramByName(execName);
57 
58  // If cannot find the executable, then nothing to do, return.
59  if (!exeMb) {
60  root.emitError() << "cannot find the `calyx` executable in PATH. "
61  << "Consider installing `calyx` using `cargo install "
62  "calyx` or reading the instructions here: "
63  "https://docs.calyxir.org/#compiler-installation";
64  return failure();
65  }
66  StringRef calyxExe = exeMb.get();
67 
68  std::string errMsg;
69  SmallString<32> nativeInputFileName;
70  std::error_code errCode = llvm::sys::fs::getPotentiallyUniqueTempFileName(
71  "calyxNativeTemp", /*suffix=*/"", nativeInputFileName);
72 
73  if (std::error_code ok; errCode != ok) {
74  root.emitError(
75  "cannot generate a unique temporary file for input to Calyx compiler");
76  return failure();
77  }
78 
79  // Emit the current program into a file so the native compiler can operate
80  // over it.
81  std::unique_ptr<llvm::ToolOutputFile> inputFile =
82  mlir::openOutputFile(nativeInputFileName, &errMsg);
83  if (inputFile == nullptr) {
84  root.emitError(errMsg);
85  return failure();
86  }
87 
88  auto res = circt::calyx::exportCalyx(root, inputFile->os());
89  if (failed(res))
90  return failure();
91  inputFile->os().flush();
92 
93  // Create a file for the native compiler to write the results into
94  SmallString<32> nativeOutputFileName;
95  errCode = llvm::sys::fs::getPotentiallyUniqueTempFileName(
96  "calyxNativeOutTemp", /*suffix=*/"", nativeOutputFileName);
97  if (std::error_code ok; errCode != ok) {
98  root.emitError(
99  "cannot generate a unique temporary file name to store output");
100  return failure();
101  }
102 
103  llvm::SmallVector<StringRef> calyxArgs = {
104  calyxExe, nativeInputFileName, "-o", nativeOutputFileName, "-b", "mlir"};
105 
106  // Configure the native pass pipeline. If passPipeline is provided, we expect
107  // it to be a comma separated list of passes to run.
108  if (!passPipeline.empty()) {
109  llvm::SmallVector<StringRef> passArgs;
110  llvm::StringRef ppRef = passPipeline;
111  ppRef.split(passArgs, ",");
112  for (auto pass : passArgs) {
113  if (pass.empty())
114  continue;
115  calyxArgs.push_back("-p");
116  calyxArgs.push_back(pass);
117  }
118  } else {
119  // If no arguments are specified, use the default pipeline.
120  calyxArgs.push_back("-p");
121  calyxArgs.push_back("all");
122  }
123 
124  // We always need to run the lower-guards pass in the native compiler to emit
125  // valid MLIR
126  calyxArgs.push_back("-p");
127  calyxArgs.push_back("lower-guards");
128 
129  std::optional<StringRef> redirects[] = {/*stdin=*/std::nullopt,
130  /*stdout=*/nativeOutputFileName,
131  /*stderr=*/std::nullopt};
132 
133  int result = llvm::sys::ExecuteAndWait(
134  calyxExe, calyxArgs, /*Env=*/std::nullopt,
135  /*Redirects=*/redirects,
136  /*SecondsToWait=*/0, /*MemoryLimit=*/0, &errMsg);
137 
138  if (result != 0) {
139  root.emitError() << errMsg;
140  return failure();
141  }
142 
143  // Parse the output buffer into a Calyx operation so that we can insert it
144  // back into the program.
145  auto bufferRead = llvm::MemoryBuffer::getFile(nativeInputFileName);
146  if (!bufferRead || !*bufferRead) {
147  root.emitError("execution of '" + calyxExe +
148  "' did not produce any output file named '" +
149  nativeInputFileName + "'");
150  return failure();
151  }
152 
153  // Load the output from the native compiler as a ModuleOp
154  auto loadedMod =
155  parseSourceFile<ModuleOp>(nativeOutputFileName.str(), root.getContext());
156  auto *loadedBlock = loadedMod->getBody();
157 
158  // XXX(rachitnigam): This is quite baroque. We insert the new block before the
159  // previous one and then remove the old block. A better thing to do would be
160  // to replace the moduleOp completely but I couldn't figure out how to do
161  // that.
162  auto *oldBlock = root.getBody();
163  loadedBlock->moveBefore(oldBlock);
164  oldBlock->erase();
165  return success();
166 }
167 
168 std::unique_ptr<mlir::Pass> circt::createCalyxNativePass() {
169  return std::make_unique<CalyxNativePass>();
170 }
mlir::LogicalResult exportCalyx(mlir::ModuleOp module, llvm::raw_ostream &os)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
std::unique_ptr< mlir::Pass > createCalyxNativePass()