Skip to content

Commit

Permalink
[RTG] Add ISA assembly emission pass
Browse files Browse the repository at this point in the history
  • Loading branch information
maerhart committed Jan 21, 2025
1 parent fc18855 commit 8ed5c6e
Show file tree
Hide file tree
Showing 8 changed files with 375 additions and 3 deletions.
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
)

249 changes: 249 additions & 0 deletions lib/Dialect/RTG/Transforms/EmitRTGISAAssemblyPass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
//===- 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/ADT/StringSet.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 llvm::StringSet<llvm::MallocAllocator> &unsupportedInstr)
: os(os), unsupportedInstr(unsupportedInstr) {}

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

// 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 llvm::StringSet<llvm::MallocAllocator> &unsupportedInstr;

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

} // namespace

static void parseUnsupportedInstructionsFile(
const std::string &unsupportedInstructionsFile,
llvm::StringSet<llvm::MallocAllocator> &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(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 llvm::StringSet<llvm::MallocAllocator> &unsupportedInstr);
/// Emit all tests into a single file (or print them to stderr if no file path
/// is given).
LogicalResult
emit(const llvm::StringSet<llvm::MallocAllocator> &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();
}

llvm::StringSet<llvm::MallocAllocator> unsupportedInstr(
unsupportedInstructions);
parseUnsupportedInstructionsFile(unsupportedInstructionsFile.getValue(),
unsupportedInstr);

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

return;
}

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

LogicalResult EmitRTGISAAssemblyPass::emit(
const llvm::StringSet<llvm::MallocAllocator> &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 llvm::StringSet<llvm::MallocAllocator> &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

0 comments on commit 8ed5c6e

Please sign in to comment.