Skip to content
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

emitc: Add fmtArgs to verbatim #383

Merged
merged 2 commits into from
Oct 9, 2024
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
2 changes: 2 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 Down
25 changes: 23 additions & 2 deletions mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
Original file line number Diff line number Diff line change
Expand Up @@ -1157,10 +1157,31 @@ 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 = [{
// Either a literal string, or an placeholder for the fmtArgs.
struct Placeholder {};
using ReplacementItem = std::variant<StringRef, Placeholder>;

FailureOr<SmallVector<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 ($fmtArgs^ `:` type($fmtArgs))? attr-dict";
}

def EmitC_AssignOp : EmitC_Op<"assign", []> {
Expand Down
72 changes: 72 additions & 0 deletions mlir/lib/Dialect/EmitC/IR/EmitC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,78 @@ LogicalResult emitc::SubscriptOp::verify() {
return success();
}

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

LogicalResult emitc::VerbatimOp::verify() {
FailureOr<SmallVector<ReplacementItem>> fmt = parseFormatString();
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();
}

/// 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.
FailureOr<SmallVector<emitc::VerbatimOp::ReplacementItem>>
emitc::VerbatimOp::parseFormatString() {
SmallVector<ReplacementItem> items;

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

StringRef toParse = getValue();
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) {
// '{' is last character
items.push_back(toParse);
break;
}
// 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;
}
return emitOpError() << "expected '}' after unescaped '{'";
}
return items;
}

//===----------------------------------------------------------------------===//
// 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 @@ -512,7 +512,21 @@ static LogicalResult printOperation(CppEmitter &emitter,
emitc::VerbatimOp verbatimOp) {
raw_ostream &os = emitter.ostream();

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

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

for (emitc::VerbatimOp::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
48 changes: 48 additions & 0 deletions mlir/test/Dialect/EmitC/invalid_ops.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,51 @@ emitc.global const @myref : !emitc.array<2xi16> = dense<128> ref

// expected-error @+1 {{'emitc.global' op global reference must be initialized}}
emitc.global const @myref : !emitc.array<2xi16> ref

// -----

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 "" %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" %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 "{}" %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 "{} {} {}" %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 "{ " %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} " %arg0, %arg1 : !emitc.ptr<i32>, i32
return
}
10 changes: 10 additions & 0 deletions mlir/test/Dialect/EmitC/ops.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,16 @@ 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 "{} + {};" %arg0, %arg1 : !emitc.ptr<i32>, i32

// Trailing '{' are ok and don't start a placeholder.
emitc.verbatim "{} + {} {" %arg0, %arg1 : !emitc.ptr<i32>, i32
return
}

emitc.global @uninit : i32
emitc.global @myglobal_int : i32 = 4
Expand Down
28 changes: 26 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,27 @@ 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 "{}" %arg : f32
// CHECK: [[V0]]

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

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

// Trailing '{' are printed as-is.
emitc.verbatim "#pragma my var={} {" %arg : f32
// CHECK-NEXT: #pragma my var=[[V0]] {

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

emitc.return
}