diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td --- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td +++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td @@ -102,10 +102,10 @@ // Integer constant %0 = "emitc.constant"(){value = 42 : i32} : () -> i32 - // Constant emitted as `int32_t* = NULL;` + // Constant emitted as `char = CHAR_MIN;` %1 = "emitc.constant"() - {value = #emitc.opaque<"NULL"> : !emitc.opaque<"int32_t*">} - : () -> !emitc.opaque<"int32_t*"> + {value = #emitc.opaque<"CHAR_MIN"> : !emitc.opaque<"char">} + : () -> !emitc.opaque<"char"> ``` }]; @@ -146,4 +146,33 @@ let hasCustomAssemblyFormat = 1; } +def EmitC_VariableOp : EmitC_Op<"variable", []> { + let summary = "Variable operation"; + let description = [{ + The `variable` operation produces an SSA value equal to some value + specified by an attribute. This can be used to form simple integer and + floating point variables, as well as more exotic things like tensor + variables. The `variable` operation also supports the EmitC opaque + attribute and the EmitC opaque type. + The `variable` is emitted as a C/C++ local variable. + + Example: + + ```mlir + // Integer variable + %0 = "emitc.variable"(){value = 42 : i32} : () -> i32 + + // Variable emitted as `int32_t* = NULL;` + %1 = "emitc.variable"() + {value = #emitc.opaque<"NULL"> : !emitc.opaque<"int32_t*">} + : () -> !emitc.opaque<"int32_t*"> + ``` + }]; + + let arguments = (ins AnyAttr:$value); + let results = (outs AnyType); + + let hasVerifier = 1; +} + #endif // MLIR_DIALECT_EMITC_IR_EMITC diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp --- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp +++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp @@ -152,6 +152,20 @@ return success(); } +//===----------------------------------------------------------------------===// +// VariableOp +//===----------------------------------------------------------------------===// + +/// The variable op requires that the attribute's type matches the return type. +LogicalResult emitc::VariableOp::verify() { + Attribute value = valueAttr(); + Type type = getType(); + if (!value.getType().isa() && type != value.getType()) + return emitOpError() << "requires attribute's type (" << value.getType() + << ") to match op's return type (" << type << ")"; + return success(); +} + //===----------------------------------------------------------------------===// // TableGen'd op method definitions //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp --- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp +++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp @@ -221,6 +221,14 @@ return printConstantOp(emitter, operation, value); } +static LogicalResult printOperation(CppEmitter &emitter, + emitc::VariableOp variableOp) { + Operation *operation = variableOp.getOperation(); + Attribute value = variableOp.value(); + + return printConstantOp(emitter, operation, value); +} + static LogicalResult printOperation(CppEmitter &emitter, arith::ConstantOp constantOp) { Operation *operation = constantOp.getOperation(); @@ -903,7 +911,7 @@ llvm::TypeSwitch(&op) // EmitC ops. .Case( + emitc::IncludeOp, emitc::VariableOp>( [&](auto op) { return printOperation(*this, op); }) // SCF ops. .Case( diff --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir --- a/mlir/test/Dialect/EmitC/invalid_ops.mlir +++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir @@ -9,8 +9,8 @@ // ----- func @const_attribute_return_type_2() { - // expected-error @+1 {{'emitc.constant' op requires attribute's type ('!emitc.opaque<"int32_t*">') to match op's return type ('!emitc.opaque<"int32_t">')}} - %c0 = "emitc.constant"(){value = "nullptr" : !emitc.opaque<"int32_t*">} : () -> !emitc.opaque<"int32_t"> + // expected-error @+1 {{'emitc.constant' op requires attribute's type ('!emitc.opaque<"char">') to match op's return type ('!emitc.opaque<"mychar">')}} + %c0 = "emitc.constant"(){value = "CHAR_MIN" : !emitc.opaque<"char">} : () -> !emitc.opaque<"mychar"> return } @@ -77,3 +77,19 @@ %2 = emitc.apply "+"(%arg) : (i32) -> !emitc.opaque<"int32_t*"> return } + +// ----- + +func @var_attribute_return_type_1() { + // expected-error @+1 {{'emitc.variable' op requires attribute's type ('i64') to match op's return type ('i32')}} + %c0 = "emitc.variable"(){value = 42: i64} : () -> i32 + return +} + +// ----- + +func @var_attribute_return_type_2() { + // expected-error @+1 {{'emitc.variable' op requires attribute's type ('!emitc.opaque<"int32_t*">') to match op's return type ('!emitc.opaque<"int32_t">')}} + %c0 = "emitc.variable"(){value = "nullptr" : !emitc.opaque<"int32_t*">} : () -> !emitc.opaque<"int32_t"> + return +} diff --git a/mlir/test/Dialect/EmitC/ops.mlir b/mlir/test/Dialect/EmitC/ops.mlir --- a/mlir/test/Dialect/EmitC/ops.mlir +++ b/mlir/test/Dialect/EmitC/ops.mlir @@ -12,7 +12,7 @@ return } -func @c(%arg0: i32) { +func @c() { %1 = "emitc.constant"(){value = 42 : i32} : () -> i32 return } @@ -22,3 +22,8 @@ %2 = emitc.apply "&"(%arg1) : (i32) -> !emitc.opaque<"int32_t*"> return } + +func @v() { + %1 = "emitc.variable"(){value = 21 : i32} : () -> i32 + return +} diff --git a/mlir/test/Target/Cpp/const.mlir b/mlir/test/Target/Cpp/const.mlir --- a/mlir/test/Target/Cpp/const.mlir +++ b/mlir/test/Target/Cpp/const.mlir @@ -1,15 +1,13 @@ // RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s -check-prefix=CPP-DEFAULT // RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s -check-prefix=CPP-DECLTOP - func @emitc_constant() { %c0 = "emitc.constant"(){value = #emitc.opaque<""> : i32} : () -> i32 %c1 = "emitc.constant"(){value = 42 : i32} : () -> i32 %c2 = "emitc.constant"(){value = -1 : i32} : () -> i32 %c3 = "emitc.constant"(){value = -1 : si8} : () -> si8 %c4 = "emitc.constant"(){value = 255 : ui8} : () -> ui8 - %c5 = "emitc.constant"(){value = #emitc.opaque<""> : !emitc.opaque<"int32_t*">} : () -> !emitc.opaque<"int32_t*"> - %c6 = "emitc.constant"(){value = #emitc.opaque<"NULL"> : !emitc.opaque<"int32_t*">} : () -> !emitc.opaque<"int32_t*"> + %c5 = "emitc.constant"(){value = #emitc.opaque<"CHAR_MIN"> : !emitc.opaque<"char">} : () -> !emitc.opaque<"char"> return } // CPP-DEFAULT: void emitc_constant() { @@ -18,8 +16,7 @@ // CPP-DEFAULT-NEXT: int32_t [[V2:[^ ]*]] = -1; // CPP-DEFAULT-NEXT: int8_t [[V3:[^ ]*]] = -1; // CPP-DEFAULT-NEXT: uint8_t [[V4:[^ ]*]] = 255; -// CPP-DEFAULT-NEXT: int32_t* [[V5:[^ ]*]]; -// CPP-DEFAULT-NEXT: int32_t* [[V6:[^ ]*]] = NULL; +// CPP-DEFAULT-NEXT: char [[V5:[^ ]*]] = CHAR_MIN; // CPP-DECLTOP: void emitc_constant() { // CPP-DECLTOP-NEXT: int32_t [[V0:[^ ]*]]; @@ -27,12 +24,10 @@ // CPP-DECLTOP-NEXT: int32_t [[V2:[^ ]*]]; // CPP-DECLTOP-NEXT: int8_t [[V3:[^ ]*]]; // CPP-DECLTOP-NEXT: uint8_t [[V4:[^ ]*]]; -// CPP-DECLTOP-NEXT: int32_t* [[V5:[^ ]*]]; -// CPP-DECLTOP-NEXT: int32_t* [[V6:[^ ]*]]; +// CPP-DECLTOP-NEXT: char [[V5:[^ ]*]]; // CPP-DECLTOP-NEXT: ; // CPP-DECLTOP-NEXT: [[V1]] = 42; // CPP-DECLTOP-NEXT: [[V2]] = -1; // CPP-DECLTOP-NEXT: [[V3]] = -1; // CPP-DECLTOP-NEXT: [[V4]] = 255; -// CPP-DECLTOP-NEXT: ; -// CPP-DECLTOP-NEXT: [[V6]] = NULL; +// CPP-DECLTOP-NEXT: [[V5]] = CHAR_MIN;