mirror of https://github.com/llvm/circt.git
[RTG] Simplify EmitISAEmission pass (#8798)
This commit is contained in:
parent
367921e352
commit
a11a834d96
|
@ -155,7 +155,11 @@ def compile(mlir_module: ir.Module, args: argparse.Namespace) -> None:
|
|||
if args.output_format == OutputFormat.ELABORATED:
|
||||
return pm
|
||||
|
||||
pm.add(f'rtg-emit-isa-assembly{{path={args.output_path}}}')
|
||||
pm.add(
|
||||
f'rtg-insert-test-to-file-mapping{{split-output=false path={args.output_path}}}'
|
||||
)
|
||||
pm.add('rtg-simple-test-inliner')
|
||||
pm.add('emit.file(rtg-emit-isa-assembly)')
|
||||
return pm
|
||||
|
||||
get_populated_pm().run(mlir_module.operation)
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
// ELABORATED-NEXT: rtg.label
|
||||
// ELABORATED-NEXT: }
|
||||
|
||||
// ASM: Begin of test0
|
||||
// ASM: Begin of test 'test0_singleton'
|
||||
// ASM: label_string:
|
||||
// ASM: End of test0
|
||||
// ASM: End of test 'test0_singleton'
|
||||
rtg.sequence @seq0() {
|
||||
%0 = rtg.label_decl "label_string"
|
||||
rtg.label local %0
|
||||
|
|
|
@ -98,8 +98,8 @@ def seq2(set):
|
|||
# ELABORATED-LABEL: rtg.test @test0
|
||||
# ELABORATED-NEXT: }
|
||||
|
||||
# ASM-LABEL: Begin of test0
|
||||
# ASM: End of test0
|
||||
# ASM-LABEL: Begin of test 'test0
|
||||
# ASM: End of test 'test0
|
||||
|
||||
|
||||
@test(Singleton)
|
||||
|
@ -119,11 +119,9 @@ def test0(config):
|
|||
# CHECK-NEXT: rtg.label local
|
||||
# CHECK-NEXT: }
|
||||
|
||||
# ASM-LABEL: Begin of test1_args
|
||||
# ASM-EMPTY:
|
||||
# ASM-LABEL: Begin of test 'test1_args
|
||||
# ASM-NEXT: L_0:
|
||||
# ASM-EMPTY:
|
||||
# ASM: End of test1_args
|
||||
# ASM: End of test 'test1_args
|
||||
|
||||
|
||||
@test(Tgt0)
|
||||
|
@ -220,8 +218,7 @@ def test1_args(config):
|
|||
|
||||
# ELABORATED-NEXT: }
|
||||
|
||||
# ASM-LABEL: Begin of test2_labels
|
||||
# ASM-EMPTY:
|
||||
# ASM-LABEL: Begin of test 'test2_labels
|
||||
# ASM-NEXT: .global l0
|
||||
# ASM-NEXT: l0:
|
||||
# ASM-NEXT: .extern l1_0
|
||||
|
@ -244,8 +241,7 @@ def test1_args(config):
|
|||
|
||||
# ASM-NEXT: # this is a comment
|
||||
|
||||
# ASM-EMPTY:
|
||||
# ASM: End of test2_labels
|
||||
# ASM: End of test 'test2_labels
|
||||
|
||||
|
||||
@test(Singleton)
|
||||
|
|
|
@ -9,12 +9,10 @@ class Target(Config):
|
|||
mem_blk = Param(loader=lambda: MemoryBlock.declare(0, 31, 32))
|
||||
|
||||
|
||||
# CHECK-LABEL: Begin of test0
|
||||
# CHECK-EMPTY:
|
||||
# CHECK-LABEL: Begin of test 'test0_Target'
|
||||
# CHECK-NEXT: la t0, 0
|
||||
# CHECK-NEXT: la t1, 8
|
||||
# CHECK-EMPTY:
|
||||
# CHECK-NEXT: End of test0
|
||||
# CHECK-NEXT: End of test 'test0_Target'
|
||||
|
||||
|
||||
@test(Target)
|
||||
|
|
|
@ -35,28 +35,24 @@ 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";
|
||||
def EmitRTGISAAssemblyPass : Pass<"rtg-emit-isa-assembly", "emit::FileOp"> {
|
||||
let summary = "emits the instructions in a format understood by assemblers";
|
||||
let description = [{
|
||||
Emits all 'rtg.test's in the IR in a format understood by assemblers.
|
||||
This pass expects all instructions to be inside 'emit.file' operations with
|
||||
an appropriate filename attribute. There are two special filenames:
|
||||
- "-" means that the output should be emitted to stdout.
|
||||
- "" means that the output should be emitted to stderr.
|
||||
|
||||
In order to operate on 'emit.file' operations in parallel, the pass
|
||||
requires that all 'emit.file' operations have a unique filename (this is not
|
||||
checked by the pass and violations will result in race conditions).
|
||||
|
||||
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 "
|
||||
|
|
|
@ -10,17 +10,14 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "circt/Dialect/Emit/EmitOps.h"
|
||||
#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/TypeSwitch.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/ToolOutputFile.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include <fstream>
|
||||
|
@ -44,6 +41,41 @@ public:
|
|||
Emitter(llvm::raw_ostream &os, const DenseSet<StringAttr> &unsupportedInstr)
|
||||
: os(os), unsupportedInstr(unsupportedInstr) {}
|
||||
|
||||
LogicalResult emitFile(emit::FileOp fileOp) {
|
||||
for (auto &op : *fileOp.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;
|
||||
}
|
||||
|
||||
auto res =
|
||||
TypeSwitch<Operation *, LogicalResult>(&op)
|
||||
.Case<InstructionOpInterface, LabelDeclOp, LabelOp, CommentOp>(
|
||||
[&](auto op) { return emit(op); })
|
||||
.Default([](auto op) {
|
||||
return op->emitError("emitter unknown RTG operation");
|
||||
});
|
||||
|
||||
if (failed(res))
|
||||
return failure();
|
||||
}
|
||||
|
||||
state.clear();
|
||||
return success();
|
||||
}
|
||||
|
||||
private:
|
||||
LogicalResult emit(InstructionOpInterface instr) {
|
||||
os << llvm::indent(4);
|
||||
bool useBinary =
|
||||
|
@ -109,47 +141,6 @@ public:
|
|||
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;
|
||||
}
|
||||
|
||||
auto res =
|
||||
TypeSwitch<Operation *, LogicalResult>(&op)
|
||||
.Case<InstructionOpInterface, LabelDeclOp, LabelOp, CommentOp>(
|
||||
[&](auto op) { return emit(op); })
|
||||
.Default([](auto op) {
|
||||
return op->emitError("emitter unknown RTG operation");
|
||||
});
|
||||
|
||||
if (failed(res))
|
||||
return failure();
|
||||
}
|
||||
|
||||
state.clear();
|
||||
|
||||
if (emitHeaderFooter)
|
||||
os << "\n# End of " << test.getSymName() << "\n\n";
|
||||
|
||||
return success();
|
||||
}
|
||||
|
||||
private:
|
||||
/// Output Stream.
|
||||
llvm::raw_ostream &os;
|
||||
|
@ -168,7 +159,7 @@ parseUnsupportedInstructionsFile(MLIRContext *ctxt,
|
|||
const std::string &unsupportedInstructionsFile,
|
||||
DenseSet<StringAttr> &unsupportedInstrs) {
|
||||
if (!unsupportedInstructionsFile.empty()) {
|
||||
std::ifstream input(unsupportedInstructionsFile);
|
||||
std::ifstream input(unsupportedInstructionsFile, std::ios::in);
|
||||
std::string token;
|
||||
while (std::getline(input, token, ',')) {
|
||||
auto trimmed = StringRef(token).trim();
|
||||
|
@ -186,75 +177,34 @@ 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();
|
||||
}
|
||||
|
||||
// Get the set of instructions not supported by the assembler
|
||||
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) {
|
||||
// Create the output file
|
||||
auto filename = getOperation().getFileName();
|
||||
std::unique_ptr<llvm::ToolOutputFile> file;
|
||||
bool emitToFile = path.hasValue() && !path.empty() && path != "-";
|
||||
bool emitToFile = !filename.empty() && filename != "-";
|
||||
if (emitToFile) {
|
||||
file = createOutputFile(path, std::string(),
|
||||
file = createOutputFile(filename, std::string(),
|
||||
[&]() { return getOperation().emitError(); });
|
||||
if (!file)
|
||||
return failure();
|
||||
return signalPassFailure();
|
||||
|
||||
file->keep();
|
||||
}
|
||||
|
||||
Emitter emitter(emitToFile ? file->os()
|
||||
: (path == "-" ? llvm::outs() : llvm::errs()),
|
||||
: (filename.empty() ? llvm::errs() : llvm::outs()),
|
||||
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);
|
||||
});
|
||||
if (failed(emitter.emitFile(getOperation())))
|
||||
return signalPassFailure();
|
||||
}
|
||||
|
|
|
@ -27,13 +27,15 @@ void circt::rtg::buildRandomizationPipeline(
|
|||
pm.addPass(rtg::createElaborationPass(passOptions));
|
||||
}
|
||||
pm.addPass(rtg::createInlineSequencesPass());
|
||||
pm.addPass(createSymbolDCEPass());
|
||||
{
|
||||
auto &testPm = pm.nest<rtg::TestOp>();
|
||||
MemoryAllocationPassOptions passOptions;
|
||||
passOptions.useImmediates = options.memoriesAsImmediates;
|
||||
pm.addNestedPass<rtg::TestOp>(rtg::createMemoryAllocationPass());
|
||||
testPm.addPass(rtg::createMemoryAllocationPass(passOptions));
|
||||
testPm.addPass(rtg::createLowerUniqueLabelsPass());
|
||||
testPm.addPass(rtg::createLinearScanRegisterAllocationPass());
|
||||
}
|
||||
pm.addPass(rtg::createLowerUniqueLabelsPass());
|
||||
pm.addNestedPass<rtg::TestOp>(rtg::createLinearScanRegisterAllocationPass());
|
||||
{
|
||||
auto &anyPm = pm.nestAny();
|
||||
anyPm.addPass(mlir::createCSEPass());
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// RUN: circt-opt --rtg-emit-isa-assembly=unsupported-instructions=rtgtest.rv32i.beq %s --split-input-file --verify-diagnostics
|
||||
|
||||
rtg.test @test0() {
|
||||
emit.file "-" {
|
||||
%rd = rtg.fixed_reg #rtgtest.ra
|
||||
%rs = rtg.fixed_reg #rtgtest.s0
|
||||
%label = rtg.label_decl "label_name"
|
||||
|
@ -11,7 +11,7 @@ rtg.test @test0() {
|
|||
|
||||
// -----
|
||||
|
||||
rtg.test @test0() {
|
||||
emit.file "-" {
|
||||
%0 = index.constant 0
|
||||
// expected-error @below {{label arguments must be elaborated before emission}}
|
||||
%label = rtg.label_decl "label_name_{{0}}", %0
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
// RUN: circt-opt --rtg-emit-isa-assembly %s 2>&1 >/dev/null | FileCheck %s --match-full-lines --strict-whitespace
|
||||
|
||||
// CHECK:# Begin of test0
|
||||
// CHECK-EMPTY:
|
||||
|
||||
rtg.test @test0() {
|
||||
emit.file "" {
|
||||
// CHECK: # Begin of test0
|
||||
rtg.comment "Begin of test0"
|
||||
%rd = rtg.fixed_reg #rtgtest.ra
|
||||
%rs = rtg.fixed_reg #rtgtest.s0
|
||||
%label = rtg.label_decl "label_name"
|
||||
|
@ -36,6 +35,3 @@ rtg.test @test0() {
|
|||
// CHECK-NEXT: jal ra, label_name
|
||||
rtgtest.rv32i.jal %rd, %label : !rtg.isa.label
|
||||
}
|
||||
|
||||
// CHECK-EMPTY:
|
||||
// CHECK-NEXT:# End of test0
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
// 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() {}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
// 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() {
|
||||
// CHECK-TEST0: ebreak
|
||||
rtgtest.rv32i.ebreak
|
||||
}
|
||||
|
||||
// CHECK-EMPTY:
|
||||
// CHECK: End of test0
|
||||
// CHECK-EMPTY:
|
||||
// CHECK-NEXT: Begin of test1
|
||||
// CHECK-EMPTY:
|
||||
|
||||
rtg.test @test1() {
|
||||
// CHECK-TEST1: ecall
|
||||
rtgtest.rv32i.ecall
|
||||
}
|
||||
|
||||
// CHECK-EMPTY:
|
||||
// CHECK-NEXT: End of test1
|
|
@ -1,12 +1,12 @@
|
|||
// 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.rv32i.ebreak,rtgtest.rv32i.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:
|
||||
// CHECK: # Begin of test0
|
||||
// CHECK-ALLOWED: # Begin of test0
|
||||
|
||||
emit.file "" {
|
||||
rtg.comment "Begin of test0"
|
||||
|
||||
rtg.test @test0() {
|
||||
%rd = rtg.fixed_reg #rtgtest.ra
|
||||
%rs = rtg.fixed_reg #rtgtest.s0
|
||||
%imm = rtg.constant #rtg.isa.immediate<12, 0>
|
||||
|
@ -214,13 +214,9 @@ rtg.test @test0() {
|
|||
// CHECK-NEXT: # srai ra, s0, 31
|
||||
// CHECK-NEXT: .word 0x41F45093
|
||||
rtgtest.rv32i.srai %rd, %rs, %imm5
|
||||
|
||||
// CHECK-ALLOWED-NEXT: # this is a comment
|
||||
// CHECK-NEXT: # this is a comment
|
||||
rtg.comment "this is a comment"
|
||||
|
||||
rtg.comment "End of test0"
|
||||
}
|
||||
|
||||
// CHECK-EMPTY:
|
||||
// CHECK-NEXT:# End of test0
|
||||
// CHECK-ALLOWED-EMPTY:
|
||||
// CHECK-ALLOWED-NEXT:# End of test0
|
||||
// CHECK-NEXT: # End of test0
|
||||
// CHECK-ALLOWED-NEXT: # End of test0
|
||||
|
|
Loading…
Reference in New Issue