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