Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RTG] Add ISA assembly emission pass #8057

Merged
merged 1 commit into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions include/circt/Dialect/RTG/Transforms/RTGPasses.td
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,36 @@ def ElaborationPass : Pass<"rtg-elaborate", "mlir::ModuleOp"> {
let dependentDialects = ["mlir::index::IndexDialect"];
}

def EmitRTGISAAssemblyPass : Pass<"rtg-emit-isa-assembly", "mlir::ModuleOp"> {
let summary = "Elaborate the contexts of RTG";
let description = [{
Emits all 'rtg.test's in the IR in a format understood by assemblers.

There are two options to specify lists of instructions that are not
supported by the assembler. For instructions in any of those lists, this
pass will emit the equivalent binary representation.

This pass operates on the `InstructionOpInterface` and folds constant-like
operations to support downstream dialects.
}];

let options = [
Option<"splitOutput", "split-output", "bool", /*default=*/"false",
"If 'true' emits one file per 'rtg.test' in the IR. The name of the "
"file matches the test name and is placed in 'path'. Otherwise, path "
"is interpreted as the full file path including filename.">,
Option<"path", "path", "std::string", /*default=*/"",
"The directory or file path in which the output files should be "
"created. If empty is is emitted to stderr (not allowed if "
"'split-output' is set to 'true')">,
Option<"unsupportedInstructionsFile", "unsupported-instructions-file",
"std::string", /*default=*/"",
"An absolute path to a file with a list of instruction names not "
"supported by the assembler.">,
ListOption<"unsupportedInstructions", "unsupported-instructions",
"std::string",
"A list of ISA instruction names not supported by the assembler.">,
];
}

#endif // CIRCT_DIALECT_RTG_TRANSFORMS_RTGPASSES_TD
4 changes: 2 additions & 2 deletions include/circt/Dialect/RTGTest/IR/RTGTestOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class InstFormatIOpBase<string mnemonic, int opcode7, int funct3>
ArrayRef<Attribute> operands) {
FoldAdaptor adaptor(operands);

os << getOperationName().split('.').second << " "
os << getOperationName().rsplit('.').second << " "
<< cast<rtg::RegisterAttrInterface>(adaptor.getRd())
.getRegisterAssembly()
<< ", "
Expand Down Expand Up @@ -129,7 +129,7 @@ class InstFormatIImmOpBase<string mnemonic, int opcode7, int funct12>

static void printInstructionAssembly(llvm::raw_ostream &os,
ArrayRef<Attribute> operands) {
os << getOperationName().split('.').second;
os << getOperationName().rsplit('.').second;
}
}];
}
Expand Down
4 changes: 3 additions & 1 deletion lib/Dialect/RTG/Transforms/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
add_circt_dialect_library(CIRCTRTGTransforms
ElaborationPass.cpp
EmitRTGISAAssemblyPass.cpp

DEPENDS
CIRCTRTGTransformsIncGen
Expand All @@ -9,9 +10,10 @@ add_circt_dialect_library(CIRCTRTGTransforms

LINK_LIBS PRIVATE
CIRCTRTGDialect
CIRCTSupport
MLIRIndexDialect
MLIRSCFDialect
MLIRIR
MLIRPass
MLIRSCFDialect
)

248 changes: 248 additions & 0 deletions lib/Dialect/RTG/Transforms/EmitRTGISAAssemblyPass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
//===- EmitRTGISAAssemblyPass.cpp - RTG Assembly Emitter ------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This is the main ISA Assembly emitter implementation for the RTG dialect.
//
//===----------------------------------------------------------------------===//

#include "circt/Dialect/RTG/IR/RTGISAAssemblyOpInterfaces.h"
#include "circt/Dialect/RTG/IR/RTGOps.h"
#include "circt/Dialect/RTG/Transforms/RTGPasses.h"
#include "circt/Support/Path.h"
#include "mlir/IR/Threading.h"
#include "mlir/Support/FileUtilities.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/ToolOutputFile.h"
#include "llvm/Support/raw_ostream.h"
#include <fstream>

namespace circt {
namespace rtg {
#define GEN_PASS_DEF_EMITRTGISAASSEMBLYPASS
#include "circt/Dialect/RTG/Transforms/RTGPasses.h.inc"
} // namespace rtg
} // namespace circt

using namespace circt;
using namespace rtg;

#define DEBUG_TYPE "emit-rtg-isa-assembly"

namespace {

class Emitter {
public:
Emitter(llvm::raw_ostream &os, const DenseSet<StringAttr> &unsupportedInstr)
: os(os), unsupportedInstr(unsupportedInstr) {}

LogicalResult emitInstruction(InstructionOpInterface instr) {
os << llvm::indent(4);
bool useBinary =
unsupportedInstr.contains(instr->getName().getIdentifier());

// TODO: we cannot just assume that double-slash is the way to do a line
// comment
if (useBinary)
os << "# ";

SmallVector<Attribute> operands;
for (auto operand : instr->getOperands()) {
auto attr = state.lookup(operand);
if (!attr)
return failure();

operands.push_back(attr);
}

instr.printInstructionAssembly(os, operands);
os << "\n";

if (!useBinary)
return success();

os << llvm::indent(4);
// TODO: don't hardcode '.word'
os << ".word 0x";
instr.printInstructionBinary(os, operands);
os << "\n";

return success();
}

LogicalResult emitTest(rtg::TestOp test, bool emitHeaderFooter = false) {
if (emitHeaderFooter)
os << "# Begin of " << test.getSymName() << "\n\n";

for (auto &op : *test.getBody()) {
if (op.hasTrait<OpTrait::ConstantLike>()) {
SmallVector<OpFoldResult> results;
if (failed(op.fold(results)))
return failure();

for (auto [val, res] : llvm::zip(op.getResults(), results)) {
auto attr = res.dyn_cast<Attribute>();
if (!attr)
return failure();

state[val] = attr;
}

continue;
}

if (auto instr = dyn_cast<InstructionOpInterface>(&op)) {
if (failed(emitInstruction(instr)))
return failure();

continue;
}

return op.emitError("emitter unknown RTG operation");
}

state.clear();

if (emitHeaderFooter)
os << "\n# End of " << test.getSymName() << "\n\n";

return success();
}

private:
/// Output Stream.
llvm::raw_ostream &os;

/// Instructions to emit in binary.
const DenseSet<StringAttr> &unsupportedInstr;

/// Evaluated values.
DenseMap<Value, Attribute> state;
};

} // namespace

static void
parseUnsupportedInstructionsFile(MLIRContext *ctxt,
const std::string &unsupportedInstructionsFile,
DenseSet<StringAttr> &unsupportedInstrs) {
if (!unsupportedInstructionsFile.empty()) {
std::ifstream input(unsupportedInstructionsFile);
std::string token;
while (std::getline(input, token, ',')) {
auto trimmed = StringRef(token).trim();
if (!trimmed.empty())
unsupportedInstrs.insert(StringAttr::get(ctxt, trimmed));
}
}
}

static std::unique_ptr<llvm::ToolOutputFile>
createOutputFile(StringRef filename, StringRef dirname,
function_ref<InFlightDiagnostic()> emitError) {
// Determine the output path from the output directory and filename.
SmallString<128> outputFilename(dirname);
appendPossiblyAbsolutePath(outputFilename, filename);
auto outputDir = llvm::sys::path::parent_path(outputFilename);

// Create the output directory if needed.
std::error_code error = llvm::sys::fs::create_directories(outputDir);
if (error) {
emitError() << "cannot create output directory \"" << outputDir
<< "\": " << error.message();
return {};
}

// Open the output file.
std::string errorMessage;
auto output = mlir::openOutputFile(outputFilename, &errorMessage);
if (!output)
emitError() << errorMessage;
return output;
}

//===----------------------------------------------------------------------===//
// EmitRTGISAAssemblyPass
//===----------------------------------------------------------------------===//

namespace {
struct EmitRTGISAAssemblyPass
: public rtg::impl::EmitRTGISAAssemblyPassBase<EmitRTGISAAssemblyPass> {
using Base::Base;

void runOnOperation() override;
/// Emit each 'rtg.test' into a separate file using the test's name as the
/// filename.
LogicalResult emitSplit(const DenseSet<StringAttr> &unsupportedInstr);
/// Emit all tests into a single file (or print them to stderr if no file path
/// is given).
LogicalResult emit(const DenseSet<StringAttr> &unsupportedInstr);
};
} // namespace

void EmitRTGISAAssemblyPass::runOnOperation() {
if ((!path.hasValue() || path.empty()) && splitOutput) {
getOperation().emitError("'split-output' option only valid in combination "
"with a valid 'path' argument");
return signalPassFailure();
}

DenseSet<StringAttr> unsupportedInstr;
for (const auto &instr : unsupportedInstructions)
unsupportedInstr.insert(StringAttr::get(&getContext(), instr));
parseUnsupportedInstructionsFile(
&getContext(), unsupportedInstructionsFile.getValue(), unsupportedInstr);

if (splitOutput) {
if (failed(emitSplit(unsupportedInstr)))
return signalPassFailure();

return;
}

if (failed(emit(unsupportedInstr)))
return signalPassFailure();
}

LogicalResult
EmitRTGISAAssemblyPass::emit(const DenseSet<StringAttr> &unsupportedInstr) {
std::unique_ptr<llvm::ToolOutputFile> file;
bool emitToFile = path.hasValue() && !path.empty();
if (emitToFile) {
file = createOutputFile(path, std::string(),
[&]() { return getOperation().emitError(); });
if (!file)
return failure();

file->keep();
}

Emitter emitter(emitToFile ? file->os() : llvm::errs(), unsupportedInstr);
for (auto test : getOperation().getOps<TestOp>())
if (failed(emitter.emitTest(test, true)))
return failure();

return success();
}

LogicalResult EmitRTGISAAssemblyPass::emitSplit(
const DenseSet<StringAttr> &unsupportedInstr) {
auto tests = getOperation().getOps<TestOp>();
return failableParallelForEach(
&getContext(), tests.begin(), tests.end(), [&](rtg::TestOp test) {
auto res = createOutputFile(test.getSymName().str() + ".s", path,
[&]() { return test.emitError(); });
if (!res)
return failure();

res->keep();
return Emitter(res->os(), unsupportedInstr).emitTest(test);
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// RUN: circt-opt --rtg-emit-isa-assembly=split-output=true --verify-diagnostics %s

// expected-error @below {{'split-output' option only valid in combination with a valid 'path' argument}}
module {
rtg.test @test0 : !rtg.dict<> {}
}
24 changes: 24 additions & 0 deletions test/Dialect/RTG/Transform/emit-rtg-isa-assembly-split.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// RUN: circt-opt --rtg-emit-isa-assembly="path=%T split-output=true" %s && FileCheck %s --input-file=%T/test0.s --check-prefix=CHECK-TEST0 && FileCheck %s --input-file=%T/test1.s --check-prefix=CHECK-TEST1
// RUN: circt-opt --rtg-emit-isa-assembly="path=%t split-output=false" %s && FileCheck %s --input-file=%t --check-prefixes=CHECK,CHECK-TEST0,CHECK-TEST1

// CHECK: Begin of test0
// CHECK-EMPTY:

rtg.test @test0 : !rtg.dict<> {
// CHECK-TEST0: ebreak
rtgtest.rv32i.ebreak
}

// CHECK-EMPTY:
// CHECK: End of test0
// CHECK-EMPTY:
// CHECK-NEXT: Begin of test1
// CHECK-EMPTY:

rtg.test @test1 : !rtg.dict<> {
// CHECK-TEST1: ecall
rtgtest.rv32i.ecall
}

// CHECK-EMPTY:
// CHECK-NEXT: End of test1
Loading