[RTG] Add InsertTestToFileMappingPass (#8795)

Add a pass to group tests to output files. Currently, this matches what the emission pass does but can be extended to group tests according to certain requirements demanded from the execution environment. Also add a simple pass that inlines the tests without any gluecode (matching what the emission pass currently does). The emission pass will be simplified in a future PR to only iterate over the file operations and print what's inside
This commit is contained in:
Martin Erhart 2025-07-29 17:09:29 +01:00 committed by GitHub
parent e070aed1f3
commit 1ef47b3cdb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 268 additions and 0 deletions

View File

@ -13,6 +13,7 @@
#ifndef CIRCT_DIALECT_RTG_IR_RTGOPS_H
#define CIRCT_DIALECT_RTG_IR_RTGOPS_H
#include "circt/Dialect/Emit/EmitOpInterfaces.h"
#include "circt/Dialect/RTG/IR/RTGAttrInterfaces.h"
#include "circt/Dialect/RTG/IR/RTGDialect.h"
#include "circt/Dialect/RTG/IR/RTGISAAssemblyAttrInterfaces.h"

View File

@ -21,6 +21,7 @@ include "mlir/Interfaces/SideEffectInterfaces.td"
include "mlir/Interfaces/InferTypeOpInterface.td"
include "circt/Dialect/RTG/IR/RTGInterfaces.td"
include "circt/Dialect/RTG/IR/RTGISAAssemblyInterfaces.td"
include "circt/Dialect/Emit/EmitOpInterfaces.td"
// Base class for the operation in this dialect.
class RTGOp<string mnemonic, list<Trait> traits = []> :
@ -696,6 +697,7 @@ def TestOp : RTGOp<"test", [
Symbol,
SingleBlock,
NoTerminator,
Emittable,
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmBlockArgumentNames"]>,
DeclareOpInterfaceMethods<SymbolUserOpInterface>,
HasParent<"mlir::ModuleOp">,

View File

@ -197,4 +197,39 @@ def PrintTestNamesPass : Pass<"rtg-print-test-names", "mlir::ModuleOp"> {
];
}
def InsertTestToFileMappingPass : Pass<"rtg-insert-test-to-file-mapping",
"mlir::ModuleOp"> {
let summary = "insert Emit dialect ops to prepare for emission";
let description = [{
This pass inserts emit dialect operations to group tests to output files.
All tests can be put in a single output file, each test in its own file, or
tests can be grouped according to some properties (e.g., machine mode vs.
user mode tests) (TODO).
}];
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')">,
];
let dependentDialects = ["emit::EmitDialect"];
}
def SimpleTestInlinerPass : Pass<"rtg-simple-test-inliner", "mlir::ModuleOp"> {
let summary = "inline test contents";
let description = [{
This is a simple pass to inline test contents into 'emit.file' operations
in which they are referenced. No "glue code" is inserted between tests
added to the same file. Thus this pass is not intended to be used in a
production pipeline but just to bring the IR into a structure understood by
the RTG ISA assembly emission pass to avoid making that pass more complex.
}];
}
#endif // CIRCT_DIALECT_RTG_TRANSFORMS_RTGPASSES_TD

View File

@ -3,12 +3,14 @@ add_circt_dialect_library(CIRCTRTGTransforms
EmbedValidationValuesPass.cpp
EmitRTGISAAssemblyPass.cpp
InlineSequencesPass.cpp
InsertTestToFileMappingPass.cpp
LinearScanRegisterAllocationPass.cpp
LowerUniqueLabelsPass.cpp
LowerValidateToLabelsPass.cpp
MemoryAllocationPass.cpp
PrintTestNamesPass.cpp
RTGPassPipelines.cpp
SimpleTestInlinerPass.cpp
UniqueValidateOpsPass.cpp
DEPENDS
@ -19,6 +21,7 @@ add_circt_dialect_library(CIRCTRTGTransforms
LINK_LIBS PRIVATE
CIRCTRTGDialect
CIRCTEmit
CIRCTSupport
MLIRArithDialect
MLIRIndexDialect

View File

@ -0,0 +1,66 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "circt/Dialect/Emit/EmitOps.h"
#include "circt/Dialect/RTG/IR/RTGOps.h"
#include "circt/Dialect/RTG/Transforms/RTGPasses.h"
#include "circt/Support/Path.h"
#include "llvm/ADT/SmallString.h"
namespace circt {
namespace rtg {
#define GEN_PASS_DEF_INSERTTESTTOFILEMAPPINGPASS
#include "circt/Dialect/RTG/Transforms/RTGPasses.h.inc"
} // namespace rtg
} // namespace circt
using namespace mlir;
using namespace circt;
using namespace circt::rtg;
//===----------------------------------------------------------------------===//
// Insert Test To File Mapping Pass
//===----------------------------------------------------------------------===//
namespace {
struct InsertTestToFileMappingPass
: public rtg::impl::InsertTestToFileMappingPassBase<
InsertTestToFileMappingPass> {
using Base::Base;
void runOnOperation() override;
};
} // namespace
void InsertTestToFileMappingPass::runOnOperation() {
SmallVector<TestOp> tests(getOperation().getOps<TestOp>());
auto loc = getOperation().getLoc();
if (!splitOutput) {
OpBuilder builder = OpBuilder::atBlockEnd(getOperation().getBody());
auto fileOp = emit::FileOp::create(builder, loc, path);
builder.setInsertionPointToStart(fileOp.getBody());
for (auto testOp : tests)
emit::RefOp::create(builder, loc, testOp.getSymNameAttr());
return;
}
if (path.empty() || path == "-") {
emitError(loc, "path must be specified when split-output is set");
return signalPassFailure();
}
for (auto testOp : tests) {
OpBuilder builder = OpBuilder::atBlockEnd(getOperation().getBody());
llvm::SmallString<128> filename(path.getValue());
appendPossiblyAbsolutePath(filename, testOp.getSymName() + ".s");
auto fileOp = emit::FileOp::create(builder, loc, filename);
OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToStart(fileOp.getBody());
emit::RefOp::create(builder, loc, testOp.getSymNameAttr());
}
}

View File

@ -0,0 +1,76 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "circt/Dialect/Emit/EmitOps.h"
#include "circt/Dialect/RTG/IR/RTGOps.h"
#include "circt/Dialect/RTG/Transforms/RTGPasses.h"
#include "mlir/IR/PatternMatch.h"
namespace circt {
namespace rtg {
#define GEN_PASS_DEF_SIMPLETESTINLINERPASS
#include "circt/Dialect/RTG/Transforms/RTGPasses.h.inc"
} // namespace rtg
} // namespace circt
using namespace mlir;
using namespace circt;
using namespace circt::rtg;
//===----------------------------------------------------------------------===//
// Simple Test Inliner Pass
//===----------------------------------------------------------------------===//
namespace {
struct SimpleTestInlinerPass
: public rtg::impl::SimpleTestInlinerPassBase<SimpleTestInlinerPass> {
using Base::Base;
void runOnOperation() override;
};
} // namespace
void SimpleTestInlinerPass::runOnOperation() {
const auto &symTbl = getAnalysis<SymbolTable>();
IRRewriter rewriter(getOperation());
for (auto fileOp : getOperation().getOps<emit::FileOp>()) {
for (auto refOp :
llvm::make_early_inc_range(fileOp.getOps<emit::RefOp>())) {
auto testOp = symTbl.lookup<TestOp>(refOp.getTargetAttr().getAttr());
if (!testOp) {
refOp.emitError("invalid symbol reference: ") << refOp.getTargetAttr();
return signalPassFailure();
}
bool allArgsUnused =
llvm::all_of(testOp.getBody()->getArguments(),
[](auto arg) { return arg.use_empty(); });
if (!allArgsUnused) {
testOp->emitError("cannot inline test with used arguments");
return signalPassFailure();
}
testOp.getBody()->eraseArguments(0, testOp.getBody()->getNumArguments());
rewriter.setInsertionPoint(refOp);
CommentOp::create(rewriter, refOp->getLoc(),
rewriter.getStringAttr("Begin of test '" +
testOp.getSymName() + "'"));
auto newTestOp = cast<TestOp>(testOp->clone());
rewriter.inlineBlockBefore(newTestOp.getBody(), refOp, {});
CommentOp::create(
rewriter, refOp->getLoc(),
rewriter.getStringAttr("End of test '" + testOp.getSymName() + "'"));
newTestOp.erase();
refOp.erase();
}
}
for (auto &op : llvm::make_early_inc_range(getOperation().getOps()))
if (isa<TargetOp, TestOp>(&op))
op.erase();
}

View File

@ -0,0 +1,6 @@
// RUN: circt-opt --rtg-insert-test-to-file-mapping=split-output=true --verify-diagnostics %s
// expected-error @below {{path must be specified when split-output is set}}
module {
rtg.test @test0() {}
}

View File

@ -0,0 +1,19 @@
// RUN: circt-opt --rtg-insert-test-to-file-mapping="path=dirname split-output=true" %s | FileCheck %s --check-prefix=CHECK-SPLIT
// RUN: circt-opt --rtg-insert-test-to-file-mapping="path=filename split-output=false" %s | FileCheck %s
rtg.test @test0() {}
rtg.test @test1() {}
// CHECK-SPLIT-LABEL: emit.file "dirname{{.+}}test0.s" {
// CHECK-SPLIT-NEXT: emit.ref @test0
// CHECK-SPLIT-NEXT: }
// CHECK-SPLIT-LABEL: emit.file "dirname{{.+}}test1.s" {
// CHECK-SPLIT-NEXT: emit.ref @test1
// CHECK-SPLIT-NEXT: }
// CHECK-LABEL: emit.file "filename" {
// CHECK-NEXT: emit.ref @test0
// CHECK-NEXT: emit.ref @test1
// CHECK-NEXT: }

View File

@ -0,0 +1,60 @@
// RUN: circt-opt --rtg-simple-test-inliner --split-input-file --verify-diagnostics %s | FileCheck %s
// CHECK-NOT: rtg.target @tgt1
rtg.target @tgt1 : !rtg.dict<imm: !rtg.isa.immediate<32>> {
%imm = rtg.constant #rtg.isa.immediate<32, 0>
rtg.yield %imm : !rtg.isa.immediate<32>
}
rtg.test @test1() {
rtg.comment "Inside test1"
}
rtg.test @test2(imm = %imm: !rtg.isa.immediate<32>) target @tgt1 {
rtg.comment "Inside test2"
}
rtg.test @test3(imm = %imm: !rtg.isa.immediate<32>) {
rtg.comment "Inside test3"
}
// CHECK-LABEL: emit.file "filename"
emit.file "filename" {
// CHECK-NOT: emit.ref
// CHECK-NEXT: rtg.comment "Begin of test 'test1'"
// CHECK-NEXT: rtg.comment "Inside test1"
// CHECK-NEXT: rtg.comment "End of test 'test1'"
emit.ref @test1
// CHECK-NOT: emit.ref
// CHECK-NEXT: rtg.comment "Begin of test 'test2'"
// CHECK-NEXT: rtg.comment "Inside test2"
// CHECK-NEXT: rtg.comment "End of test 'test2'"
emit.ref @test2
// CHECK-NOT: emit.ref
// CHECK-NEXT: rtg.comment "Begin of test 'test3'"
// CHECK-NEXT: rtg.comment "Inside test3"
// CHECK-NEXT: rtg.comment "End of test 'test3'"
emit.ref @test3
// CHECK-NOT: emit.ref
}
// -----
// expected-error @below {{cannot inline test with used arguments}}
rtg.test @test(imm = %imm: !rtg.isa.immediate<32>) {
%reg = rtg.fixed_reg #rtgtest.t0
rtgtest.rv32i.lui %reg, %imm : !rtg.isa.immediate<32>
}
emit.file "filename" {
emit.ref @test
}
// -----
hw.module @mod() {}
emit.file "filename" {
// expected-error @below {{invalid symbol reference: @mod}}
emit.ref @mod
}