MLIR의 리마크 인프라스트럭처 개요. RemarkEngine과 스트리머를 통해 최적화 리마크를 수집·스트리밍·직렬화하는 방법, 리마크 범주(Passed/Missed/Failure/Analysis), 경량 스트리밍 API와 메트릭/서식화 도우미, LLVM 리마크(YAML/비트스트림) 통합 및 커스텀 스트리머 구현을 설명합니다.
리마크(remark)는 컴파일러가 다음을 설명하기 위해 내보내는 구조화된, 사람과 기계가 모두 읽을 수 있는 노트입니다:
RemarkEngine은 컴파일 중에 최종화된 리마크를 수집하여 플러그형 스트리머(streamer)로 전달합니다. 기본적으로 MLIR은 LLVM의 llvm::remarks와 통합되어 다음을 할 수 있습니다:
MLIRContext에서 구성.Passed, Missed, Failure, Analysis.<<를 사용하는 경량 스트리밍 인터페이스(MLIR 진단과 유사).두 가지 주요 구성요소:
RemarkEngine(MLIRContext가 소유): 최종화된 InFlightRemark를 수신하고, 선택적으로 이를 DiagnosticEngine에 미러링한 뒤, 설치된 스트리머로 디스패치합니다.
MLIRRemarkStreamerBase(추상): 단 하나의 훅을 가지는 백엔드 인터페이스:
virtual void streamOptimizationRemark(const Remark &remark) = 0;
기본 백엔드 – MLIRLLVMRemarkStreamer: mlir::Remark를 LLVM의 리마크 포맷에 맞게 적응시키고 llvm::remarks::RemarkStreamer를 통해 YAML/비트스트림으로 기록합니다.
소유권 흐름: MLIRContext → RemarkEngine → MLIRRemarkStreamerBase
MLIR은 기본으로 네 가지 리마크 범주를 제공합니다(필요 시 확장 가능):
최적화/변환이 성공함.
[Passed] RemarkName | Category:Vectorizer:myPass1 | Function=foo | Remark="vectorized loop", tripCount=128
최적화/변환이 적용되지 않음 — 가능하면 실행 가능한 피드백을 포함.
[Missed] | Category:Unroll | Function=foo | Reason="tripCount=4 < threshold=256", Suggestion="increase unroll to 128"
최적화/변환을 시도했으나 실패함. 이는 Missed 범주와는 약간 다릅니다.
예를 들어 사용자가 컴파일러를 호출할 때 -use-max-register=100을 지정했지만, 어떤 이유로 시도가 실패한 경우:
$ your-compiler -use-max-register=100 mycode.xyz
[Failed] Category:RegisterAllocator | Reason="Limiting to use-max-register=100 failed; it now uses 104 registers for better performance"
중립적인 분석 결과.
[Analysis] Category:Register | Remark="Kernel uses 168 registers"
[Analysis] Category:Register | Remark="Kernel uses 10kB local memory"
remark::* 헬퍼는 인플라이트(in-flight) 리마크를 반환합니다. <<를 사용하여 문자열이나 키–값 메트릭을 추가합니다.
리마크를 구성할 때 일반적으로 StringRef인 네 가지 필드를 제공합니다:
#include "mlir/IR/Remarks.h"
LogicalResult MyPass::runOnOperation() {
Location loc = getOperation()->getLoc();
remark::RemarkOpts opts = remark::RemarkOpts::name(MyRemarkName1)
.category(categoryVectorizer)
.function(fName)
.subCategory(myPassname1);
// 성공(PASSED)
remark::passed(loc, opts)
<< "vectorized loop"
<< remark::metric("tripCount", 128);
// 분석(ANALYSIS)
remark::analysis(loc, opts)
<< "Kernel uses 168 registers";
// 놓침(MISSED) - 이유 + 제안 포함
int tripBad = 4, threshold = 256, target = 128;
remark::missed(loc, opts)
<< remark::reason("tripCount={0} < threshold={1}", tripBad, threshold)
<< remark::suggest("increase unroll to {0}", target);
// 실패(FAILURE)
remark::failed(loc, opts)
<< remark::reason("failed due to unsupported pattern");
return success();
}
도우미 함수는 LLVM format 스타일의 문자열을 받습니다. 이 포맷은 지연(lazy) 구성되므로, 비활성화 상태에서는 리마크가 비용 0입니다.
remark::add(fmt, ...) – metric("Remark", ...)의 단축형.remark::reason(fmt, ...) – metric("Reason", ...)의 단축형. 놓침/실패의 이유를 설명하는 데 사용.remark::suggest(fmt, ...) – metric("Suggestion", ...)의 단축형. 실행 가능한 피드백을 제공하는 데 사용.remark::metric(key, value) – 구조화된 키–값 메트릭을 추가.예: TripCount 추적. YAML로 내보내면 기계 가독성을 위해 args 아래에 표시됩니다:
remark::metric("TripCount", value)
단순 문자열을 전달하는 것(예: << "vectorized loop")은 다음과 동일합니다:
metric("Remark", "vectorized loop")
선택한 포맷으로 리마크를 파일에 영속화합니다.
mlir::remark::RemarkCategories cats{/*passed=*/categoryLoopunroll,
/*missed=*/std::nullopt,
/*analysis=*/std::nullopt,
/*failed=*/categoryLoopunroll};
mlir::remark::enableOptimizationRemarksWithLLVMStreamer(
context, yamlFile, llvm::remarks::Format::YAML, cats);
YAML 포맷 – 사람이 읽기 쉽고, 비교(diff)하기 용이:
--- !Passed
pass: Category:SubCategory
name: MyRemarkName1
function: myFunc
loc: myfile.mlir:12:3
args:
- Remark: vectorized loop
- tripCount: 128
비트스트림 포맷 – 대규모 실행에 적합한 compact 바이너리.
스트리머를 전달하지 않으면, 리마크는 mlir::emitRemarks를 사용하여 DiagnosticEngine으로 미러링됩니다.
mlir::remark::RemarkCategories cats{/*passed=*/categoryLoopunroll,
/*missed=*/std::nullopt,
/*analysis=*/std::nullopt,
/*failed=*/categoryLoopunroll};
remark::enableOptimizationRemarks(
/*streamer=*/nullptr, cats,
/*printAsEmitRemarks=*/true);
MLIRRemarkStreamerBase를 상속하여 임의의 포맷으로 리마크를 소비하는 커스텀 스트리머를 구현할 수 있습니다.
class MyStreamer : public MLIRRemarkStreamerBase {
public:
void streamOptimizationRemark(const Remark &remark) override {
// Convert and write remark to your custom format
}
};
auto myStreamer = std::make_unique<MyStreamer>();
remark::enableOptimizationRemarks(
/*streamer=*/myStreamer, cats,
/*printAsEmitRemarks=*/true);