CIRCT 20.0.0git
Loading...
Searching...
No Matches
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
26namespace circt {
27#define GEN_PASS_DEF_CALYXNATIVE
28#include "circt/Conversion/Passes.h.inc"
29} // namespace circt
30
31using namespace mlir;
32using namespace circt;
33
34/// ConversionPatterns.
35
36/// Pass entrypoint.
37
38namespace {
39class CalyxNativePass : public circt::impl::CalyxNativeBase<CalyxNativePass> {
40public:
41 void runOnOperation() override;
42
43private:
44 LogicalResult runOnModule(ModuleOp root);
45};
46} // end anonymous namespace
47
48void CalyxNativePass::runOnOperation() {
49 ModuleOp mod = getOperation();
50 if (failed(runOnModule(mod)))
51 return signalPassFailure();
52}
53
54LogicalResult 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
168std::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.
std::unique_ptr< mlir::Pass > createCalyxNativePass()