Skip to content

[MLIR] emitc: Add fmtArgs to verbatim #123294

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

Merged
merged 2 commits into from
Feb 18, 2025
Merged
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
6 changes: 6 additions & 0 deletions mlir/include/mlir/Dialect/EmitC/IR/EmitC.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
#include "mlir/Dialect/EmitC/IR/EmitCDialect.h.inc"
#include "mlir/Dialect/EmitC/IR/EmitCEnums.h.inc"

#include <variant>

namespace mlir {
namespace emitc {
void buildTerminatedBody(OpBuilder &builder, Location loc);
Expand All @@ -47,6 +49,10 @@ bool isSupportedFloatType(mlir::Type type);
/// Determines whether \p type is a emitc.size_t/ssize_t type.
bool isPointerWideType(mlir::Type type);

// Either a literal string, or an placeholder for the fmtArgs.
struct Placeholder {};
using ReplacementItem = std::variant<StringRef, Placeholder>;

} // namespace emitc
} // namespace mlir

Expand Down
23 changes: 21 additions & 2 deletions mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
Original file line number Diff line number Diff line change
Expand Up @@ -1269,10 +1269,29 @@ def EmitC_VerbatimOp : EmitC_Op<"verbatim"> {
}
#endif
```

If the `emitc.verbatim` op has operands, then the `value` is interpreted as
format string, where `{}` is a placeholder for an operand in their order.
For example, `emitc.verbatim "#pragma my src={} dst={}" %src, %dest : i32, i32`
would be emitted as `#pragma my src=a dst=b` if `%src` became `a` and
`%dest` became `b` in the C code.
`{{` in the format string is interpreted as a single `{` and doesn't introduce
a placeholder.
}];

let arguments = (ins StrAttr:$value);
let assemblyFormat = "$value attr-dict";
let extraClassDeclaration = [{
FailureOr<SmallVector<::mlir::emitc::ReplacementItem>> parseFormatString();
}];

let arguments = (ins StrAttr:$value, Variadic<EmitCType>:$fmtArgs);

let builders = [OpBuilder<(ins "::mlir::StringAttr":$value),
[{ build($_builder, $_state, value, {}); }]>];
let builders = [OpBuilder<(ins "::llvm::StringRef":$value),
[{ build($_builder, $_state, value, {}); }]>];
let hasVerifier = 1;
let assemblyFormat =
"$value (`args` $fmtArgs^ `:` type($fmtArgs))? attr-dict";
}

def EmitC_AssignOp : EmitC_Op<"assign", []> {
Expand Down
107 changes: 107 additions & 0 deletions mlir/lib/Dialect/EmitC/IR/EmitC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/FormatVariadic.h"

using namespace mlir;
using namespace mlir::emitc;
Expand Down Expand Up @@ -167,6 +168,63 @@ static LogicalResult verifyInitializationAttribute(Operation *op,
return success();
}

/// Parse a format string and return a list of its parts.
/// A part is either a StringRef that has to be printed as-is, or
/// a Placeholder which requires printing the next operand of the VerbatimOp.
/// In the format string, all `{}` are replaced by Placeholders, except if the
/// `{` is escaped by `{{` - then it doesn't start a placeholder.
template <class ArgType>
FailureOr<SmallVector<ReplacementItem>>
parseFormatString(StringRef toParse, ArgType fmtArgs,
std::optional<llvm::function_ref<mlir::InFlightDiagnostic()>>
emitError = {}) {
SmallVector<ReplacementItem> items;

// If there are not operands, the format string is not interpreted.
if (fmtArgs.empty()) {
items.push_back(toParse);
return items;
}

while (!toParse.empty()) {
size_t idx = toParse.find('{');
if (idx == StringRef::npos) {
// No '{'
items.push_back(toParse);
break;
}
if (idx > 0) {
// Take all chars excluding the '{'.
items.push_back(toParse.take_front(idx));
toParse = toParse.drop_front(idx);
continue;
}
if (toParse.size() < 2) {
return (*emitError)()
<< "expected '}' after unescaped '{' at end of string";
}
// toParse contains at least two characters and starts with `{`.
char nextChar = toParse[1];
if (nextChar == '{') {
// Double '{{' -> '{' (escaping).
items.push_back(toParse.take_front(1));
toParse = toParse.drop_front(2);
continue;
}
if (nextChar == '}') {
items.push_back(Placeholder{});
toParse = toParse.drop_front(2);
continue;
}

if (emitError.has_value()) {
return (*emitError)() << "expected '}' after unescaped '{'";
}
return failure();
}
return items;
}

//===----------------------------------------------------------------------===//
// AddOp
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -909,6 +967,55 @@ LogicalResult emitc::SubscriptOp::verify() {
return success();
}

//===----------------------------------------------------------------------===//
// VerbatimOp
//===----------------------------------------------------------------------===//

LogicalResult emitc::VerbatimOp::verify() {
auto errorCallback = [&]() -> InFlightDiagnostic {
return this->emitOpError();
};
FailureOr<SmallVector<ReplacementItem>> fmt =
::parseFormatString(getValue(), getFmtArgs(), errorCallback);
if (failed(fmt))
return failure();

size_t numPlaceholders = llvm::count_if(*fmt, [](ReplacementItem &item) {
return std::holds_alternative<Placeholder>(item);
});

if (numPlaceholders != getFmtArgs().size()) {
return emitOpError()
<< "requires operands for each placeholder in the format string";
}
return success();
}

static ParseResult parseVariadicTypeFmtArgs(AsmParser &p,
SmallVector<Type> &params) {
Type type;
if (p.parseType(type))
return failure();

params.push_back(type);
while (succeeded(p.parseOptionalComma())) {
if (p.parseType(type))
return failure();
params.push_back(type);
}

return success();
}

static void printVariadicTypeFmtArgs(AsmPrinter &p, ArrayRef<Type> params) {
llvm::interleaveComma(params, p, [&](Type type) { p.printType(type); });
}

FailureOr<SmallVector<ReplacementItem>> emitc::VerbatimOp::parseFormatString() {
// Error checking is done in verify.
return ::parseFormatString(getValue(), getFmtArgs());
}

//===----------------------------------------------------------------------===//
// EmitC Enums
//===----------------------------------------------------------------------===//
Expand Down
16 changes: 15 additions & 1 deletion mlir/lib/Target/Cpp/TranslateToCpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,21 @@ static LogicalResult printOperation(CppEmitter &emitter,
emitc::VerbatimOp verbatimOp) {
raw_ostream &os = emitter.ostream();

os << verbatimOp.getValue();
FailureOr<SmallVector<ReplacementItem>> items =
verbatimOp.parseFormatString();
if (failed(items))
return failure();

auto fmtArg = verbatimOp.getFmtArgs().begin();

for (ReplacementItem &item : *items) {
if (auto *str = std::get_if<StringRef>(&item)) {
os << *str;
} else {
if (failed(emitter.emitOperand(*fmtArg++)))
return failure();
}
}

return success();
}
Expand Down
56 changes: 56 additions & 0 deletions mlir/test/Dialect/EmitC/invalid_ops.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -566,3 +566,59 @@ func.func @emitc_switch() {
}
return
}

// -----

func.func @test_verbatim(%arg0 : !emitc.ptr<i32>, %arg1 : i32) {
// expected-error @+1 {{'emitc.verbatim' op requires operands for each placeholder in the format string}}
emitc.verbatim "" args %arg0, %arg1 : !emitc.ptr<i32>, i32
return
}

// -----

func.func @test_verbatim(%arg0 : !emitc.ptr<i32>, %arg1 : i32) {
// expected-error @+1 {{'emitc.verbatim' op expected '}' after unescaped '{' at end of string}}
emitc.verbatim "{} + {} {" args %arg0, %arg1 : !emitc.ptr<i32>, i32
return
}

// -----

func.func @test_verbatim(%arg0 : !emitc.ptr<i32>, %arg1 : i32) {
// expected-error @+1 {{'emitc.verbatim' op requires operands for each placeholder in the format string}}
emitc.verbatim "abc" args %arg0, %arg1 : !emitc.ptr<i32>, i32
return
}

// -----

func.func @test_verbatim(%arg0 : !emitc.ptr<i32>, %arg1 : i32) {
// expected-error @+1 {{'emitc.verbatim' op requires operands for each placeholder in the format string}}
emitc.verbatim "{}" args %arg0, %arg1 : !emitc.ptr<i32>, i32
return
}

// -----

func.func @test_verbatim(%arg0 : !emitc.ptr<i32>, %arg1 : i32) {
// expected-error @+1 {{'emitc.verbatim' op requires operands for each placeholder in the format string}}
emitc.verbatim "{} {} {}" args %arg0, %arg1 : !emitc.ptr<i32>, i32
return
}

// -----

func.func @test_verbatim(%arg0 : !emitc.ptr<i32>, %arg1 : i32) {
// expected-error @+1 {{'emitc.verbatim' op expected '}' after unescaped '{'}}
emitc.verbatim "{ " args %arg0, %arg1 : !emitc.ptr<i32>, i32
return
}

// -----

func.func @test_verbatim(%arg0 : !emitc.ptr<i32>, %arg1 : i32) {
// expected-error @+1 {{'emitc.verbatim' op expected '}' after unescaped '{'}}
emitc.verbatim "{a} " args %arg0, %arg1 : !emitc.ptr<i32>, i32
return
}
12 changes: 12 additions & 0 deletions mlir/test/Dialect/EmitC/ops.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,18 @@ emitc.verbatim "#endif // __cplusplus"
emitc.verbatim "typedef int32_t i32;"
emitc.verbatim "typedef float f32;"

// The value is not interpreted as format string if there are no operands.
emitc.verbatim "{} { }"

func.func @test_verbatim(%arg0 : !emitc.ptr<i32>, %arg1 : i32) {
emitc.verbatim "{} + {};" args %arg0, %arg1 : !emitc.ptr<i32>, i32

// Check there is no ambiguity whether %a is the argument to the emitc.verbatim op.
emitc.verbatim "a"
%a = "emitc.constant"(){value = 42 : i32} : () -> i32

return
}

emitc.global @uninit : i32
emitc.global @myglobal_int : i32 = 4
Expand Down
24 changes: 22 additions & 2 deletions mlir/test/Target/Cpp/verbatim.mlir
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s
// RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s
// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s --match-full-lines
// RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s --match-full-lines


emitc.verbatim "#ifdef __cplusplus"
Expand All @@ -19,3 +19,23 @@ emitc.verbatim "typedef int32_t i32;"
// CHECK-NEXT: typedef int32_t i32;
emitc.verbatim "typedef float f32;"
// CHECK-NEXT: typedef float f32;

emitc.func @func(%arg: f32) {
// CHECK: void func(float [[V0:[^ ]*]]) {
%a = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.array<3x7xi32>
// CHECK: int32_t [[A:[^ ]*]][3][7];

emitc.verbatim "{}" args %arg : f32
// CHECK: [[V0]]

emitc.verbatim "{} {{a" args %arg : f32
// CHECK-NEXT: [[V0]] {a

emitc.verbatim "#pragma my var={} property" args %arg : f32
// CHECK-NEXT: #pragma my var=[[V0]] property

emitc.verbatim "#pragma my2 var={} property" args %a : !emitc.array<3x7xi32>
// CHECK-NEXT: #pragma my2 var=[[A]] property

emitc.return
}