diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.h b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.h index 87a4078f280f6..57029c64ffd00 100644 --- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.h +++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.h @@ -27,6 +27,8 @@ #include "mlir/Dialect/EmitC/IR/EmitCDialect.h.inc" #include "mlir/Dialect/EmitC/IR/EmitCEnums.h.inc" +#include + namespace mlir { namespace emitc { void buildTerminatedBody(OpBuilder &builder, Location loc); @@ -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; + } // namespace emitc } // namespace mlir diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td index 965634aa7141c..15f3a5a4742c0 100644 --- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td +++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td @@ -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> parseFormatString(); + }]; + + let arguments = (ins StrAttr:$value, Variadic:$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", []> { diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp index de85ec4b2695c..a530f7a68ee75 100644 --- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp +++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp @@ -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; @@ -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 +FailureOr> +parseFormatString(StringRef toParse, ArgType fmtArgs, + std::optional> + emitError = {}) { + SmallVector 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 //===----------------------------------------------------------------------===// @@ -909,6 +967,55 @@ LogicalResult emitc::SubscriptOp::verify() { return success(); } +//===----------------------------------------------------------------------===// +// VerbatimOp +//===----------------------------------------------------------------------===// + +LogicalResult emitc::VerbatimOp::verify() { + auto errorCallback = [&]() -> InFlightDiagnostic { + return this->emitOpError(); + }; + FailureOr> fmt = + ::parseFormatString(getValue(), getFmtArgs(), errorCallback); + if (failed(fmt)) + return failure(); + + size_t numPlaceholders = llvm::count_if(*fmt, [](ReplacementItem &item) { + return std::holds_alternative(item); + }); + + if (numPlaceholders != getFmtArgs().size()) { + return emitOpError() + << "requires operands for each placeholder in the format string"; + } + return success(); +} + +static ParseResult parseVariadicTypeFmtArgs(AsmParser &p, + SmallVector ¶ms) { + 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 params) { + llvm::interleaveComma(params, p, [&](Type type) { p.printType(type); }); +} + +FailureOr> emitc::VerbatimOp::parseFormatString() { + // Error checking is done in verify. + return ::parseFormatString(getValue(), getFmtArgs()); +} + //===----------------------------------------------------------------------===// // EmitC Enums //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp index 3276ed9d394d2..abff252575eb0 100644 --- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp +++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp @@ -568,7 +568,21 @@ static LogicalResult printOperation(CppEmitter &emitter, emitc::VerbatimOp verbatimOp) { raw_ostream &os = emitter.ostream(); - os << verbatimOp.getValue(); + FailureOr> items = + verbatimOp.parseFormatString(); + if (failed(items)) + return failure(); + + auto fmtArg = verbatimOp.getFmtArgs().begin(); + + for (ReplacementItem &item : *items) { + if (auto *str = std::get_if(&item)) { + os << *str; + } else { + if (failed(emitter.emitOperand(*fmtArg++))) + return failure(); + } + } return success(); } diff --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir index a0d8d7f59de11..13bd96f6d9fb4 100644 --- a/mlir/test/Dialect/EmitC/invalid_ops.mlir +++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir @@ -566,3 +566,59 @@ func.func @emitc_switch() { } return } + +// ----- + +func.func @test_verbatim(%arg0 : !emitc.ptr, %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 + return +} + +// ----- + +func.func @test_verbatim(%arg0 : !emitc.ptr, %arg1 : i32) { + // expected-error @+1 {{'emitc.verbatim' op expected '}' after unescaped '{' at end of string}} + emitc.verbatim "{} + {} {" args %arg0, %arg1 : !emitc.ptr, i32 + return +} + +// ----- + +func.func @test_verbatim(%arg0 : !emitc.ptr, %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 + return +} + +// ----- + +func.func @test_verbatim(%arg0 : !emitc.ptr, %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 + return +} + +// ----- + +func.func @test_verbatim(%arg0 : !emitc.ptr, %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 + return +} + +// ----- + +func.func @test_verbatim(%arg0 : !emitc.ptr, %arg1 : i32) { + // expected-error @+1 {{'emitc.verbatim' op expected '}' after unescaped '{'}} + emitc.verbatim "{ " args %arg0, %arg1 : !emitc.ptr, i32 + return +} + +// ----- + +func.func @test_verbatim(%arg0 : !emitc.ptr, %arg1 : i32) { + // expected-error @+1 {{'emitc.verbatim' op expected '}' after unescaped '{'}} + emitc.verbatim "{a} " args %arg0, %arg1 : !emitc.ptr, i32 + return +} diff --git a/mlir/test/Dialect/EmitC/ops.mlir b/mlir/test/Dialect/EmitC/ops.mlir index 7fd0a2d020397..645009bcc3c36 100644 --- a/mlir/test/Dialect/EmitC/ops.mlir +++ b/mlir/test/Dialect/EmitC/ops.mlir @@ -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, %arg1 : i32) { + emitc.verbatim "{} + {};" args %arg0, %arg1 : !emitc.ptr, 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 diff --git a/mlir/test/Target/Cpp/verbatim.mlir b/mlir/test/Target/Cpp/verbatim.mlir index 10465dd781a81..e1f225c112a43 100644 --- a/mlir/test/Target/Cpp/verbatim.mlir +++ b/mlir/test/Target/Cpp/verbatim.mlir @@ -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" @@ -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 +}