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

Open
wants to merge 1 commit into
base: maerhart-rtgtest-instructions
Choose a base branch
from
Open
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::arith::ArithDialect"];
}

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
2 changes: 2 additions & 0 deletions 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,6 +10,7 @@ add_circt_dialect_library(CIRCTRTGTransforms

LINK_LIBS PRIVATE
CIRCTRTGDialect
CIRCTSupport
MLIRArithDialect
MLIRIR
MLIRPass
Expand Down
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.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.ecall
}

// CHECK-EMPTY:
// CHECK-NEXT: End of test1
58 changes: 58 additions & 0 deletions test/Dialect/RTG/Transform/emit-rtg-isa-assembly.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// RUN: circt-opt --rtg-emit-isa-assembly %s 2>&1 >/dev/null | FileCheck %s --check-prefix=CHECK-ALLOWED --match-full-lines --strict-whitespace
// RUN: circt-opt --rtg-emit-isa-assembly="unsupported-instructions=rtgtest.ebreak,rtgtest.ecall unsupported-instructions-file=%S/unsupported-instr.txt" %s 2>&1 >/dev/null | FileCheck %s --match-full-lines --strict-whitespace

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

rtg.test @test0 : !rtg.dict<> {
%rd = rtg.fixed_reg #rtgtest.ra
%rs = rtg.fixed_reg #rtgtest.s0
%imm = rtgtest.immediate #rtgtest.imm12<0>

// CHECK-ALLOWED-NEXT: jalr ra, 0(s0)
// CHECK-NEXT: // jalr ra, 0(s0)
// CHECK-NEXT: .word 0x400E7
rtgtest.jalr %rd, %rs, %imm

// CHECK-ALLOWED-NEXT: lb ra, 0(s0)
// CHECK-NEXT: // lb ra, 0(s0)
// CHECK-NEXT: .word 0x40083
rtgtest.lb %rd, %rs, %imm

// CHECK-ALLOWED-NEXT: lh ra, 0(s0)
// CHECK-NEXT: // lh ra, 0(s0)
// CHECK-NEXT: .word 0x41083
rtgtest.lh %rd, %rs, %imm

// CHECK-ALLOWED-NEXT: lw ra, 0(s0)
// CHECK-NEXT: // lw ra, 0(s0)
// CHECK-NEXT: .word 0x42083
rtgtest.lw %rd, %rs, %imm

// CHECK-ALLOWED-NEXT: lbu ra, 0(s0)
// CHECK-NEXT: // lbu ra, 0(s0)
// CHECK-NEXT: .word 0x44083
rtgtest.lbu %rd, %rs, %imm

// CHECK-ALLOWED-NEXT: lhu ra, 0(s0)
// CHECK-NEXT: // lhu ra, 0(s0)
// CHECK-NEXT: .word 0x45083
rtgtest.lhu %rd, %rs, %imm

// CHECK-ALLOWED-NEXT: ebreak
// CHECK-NEXT: // ebreak
// CHECK-NEXT: .word 0x100073
rtgtest.ebreak

// CHECK-ALLOWED-NEXT: ecall
// CHECK-NEXT: // ecall
// CHECK-NEXT: .word 0x73
rtgtest.ecall
}

// CHECK-EMPTY:
// CHECK-NEXT:// End of test0
// CHECK-ALLOWED-EMPTY:
// CHECK-ALLOWED-NEXT:// End of test0
1 change: 1 addition & 0 deletions test/Dialect/RTG/Transform/unsupported-instr.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rtgtest.jalr,rtgtest.lb,rtgtest.lh,rtgtest.lw,rtgtest.lbu,rtgtest.lhu
Loading