[RTG] Simplify EmitISAEmission pass (#8798)

This commit is contained in:
Martin Erhart 2025-07-29 21:16:48 +01:00 committed by GitHub
parent 367921e352
commit a11a834d96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 90 additions and 182 deletions

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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 "

View File

@ -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();
}

View File

@ -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());

View File

@ -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

View File

@ -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

View File

@ -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() {}
}

View File

@ -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

View File

@ -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