[mlir] Add EmitC dialect

This upstreams the EmitC dialect and the corresponding Cpp target, both
initially presented with [1], from [2] to MLIR core. For the related
discussion, see [3].

[1] https://reviews.llvm.org/D76571
[2] https://github.com/iml130/mlir-emitc
[3] https://llvm.discourse.group/t/emitc-generating-c-c-from-mlir/3388

Co-authored-by: Jacques Pienaar <jpienaar@google.com>
Co-authored-by: Simon Camphausen <simon.camphausen@iml.fraunhofer.de>
Co-authored-by: Oliver Scherf <oliver.scherf@iml.fraunhofer.de>

Reviewed By: rriddle

Differential Revision: https://reviews.llvm.org/D103969
This commit is contained in:
Marius Brehler 2021-06-09 13:38:10 +00:00
parent 1bd4085e0b
commit 876de062f9
17 changed files with 660 additions and 0 deletions

View File

@ -5,6 +5,7 @@ add_subdirectory(ArmSVE)
add_subdirectory(AMX)
add_subdirectory(Complex)
add_subdirectory(DLTI)
add_subdirectory(EmitC)
add_subdirectory(GPU)
add_subdirectory(Math)
add_subdirectory(Linalg)

View File

@ -0,0 +1 @@
add_subdirectory(IR)

View File

@ -0,0 +1,7 @@
add_mlir_dialect(EmitC emitc)
add_mlir_doc(EmitC EmitC Dialects/ -gen-dialect-doc)
set(LLVM_TARGET_DEFINITIONS EmitCAttributes.td)
mlir_tablegen(EmitCAttributes.h.inc -gen-attrdef-decls)
mlir_tablegen(EmitCAttributes.cpp.inc -gen-attrdef-defs)
add_public_tablegen_target(MLIREmitCAttributesIncGen)

View File

@ -0,0 +1,32 @@
//===- EmitC.h - EmitC Dialect ----------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file declares EmitC in MLIR.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_DIALECT_EMITC_IR_EMITC_H
#define MLIR_DIALECT_EMITC_IR_EMITC_H
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Dialect.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "mlir/Dialect/EmitC/IR/EmitCDialect.h.inc"
#define GET_ATTRDEF_CLASSES
#include "mlir/Dialect/EmitC/IR/EmitCAttributes.h.inc"
#define GET_TYPEDEF_CLASSES
#include "mlir/Dialect/EmitC/IR/EmitCTypes.h.inc"
#define GET_OP_CLASSES
#include "mlir/Dialect/EmitC/IR/EmitC.h.inc"
#endif // MLIR_DIALECT_EMITC_IR_EMITC_H

View File

@ -0,0 +1,148 @@
//===- EmitC.td - EmitC operations--------------------------*- tablegen -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Defines the MLIR EmitC operations.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_DIALECT_EMITC_IR_EMITC
#define MLIR_DIALECT_EMITC_IR_EMITC
include "mlir/Dialect/EmitC/IR/EmitCAttributes.td"
include "mlir/Dialect/EmitC/IR/EmitCTypes.td"
include "mlir/Interfaces/SideEffectInterfaces.td"
//===----------------------------------------------------------------------===//
// EmitC op definitions
//===----------------------------------------------------------------------===//
// Base class for EmitC dialect ops.
class EmitC_Op<string mnemonic, list<OpTrait> traits = []>
: Op<EmitC_Dialect, mnemonic, traits> {
let verifier = "return ::verify(*this);";
}
def EmitC_ApplyOp : EmitC_Op<"apply", []> {
let summary = "Apply operation";
let description = [{
With the `apply` operation the operators & (address of) and * (contents of)
can be applied to a single operand.
Example:
```mlir
// Custom form of applying the & operator.
%0 = emitc.apply "&"(%arg0) : (i32) -> !emitc.opaque<"int32_t*">
// Generic form of the same operation.
%0 = "emitc.apply"(%arg0) {applicableOperator = "&"}
: (i32) -> !emitc.opaque<"int32_t*">
```
}];
let arguments = (ins
Arg<StrAttr, "the operator to apply">:$applicableOperator,
AnyType:$operand
);
let results = (outs AnyType:$result);
let assemblyFormat = [{
$applicableOperator `(` $operand `)` attr-dict `:` functional-type($operand, results)
}];
}
def EmitC_CallOp : EmitC_Op<"call", []> {
let summary = "Call operation";
let description = [{
The `call` operation represents a C++ function call. The call allows
specifying order of operands and attributes in the call as follows:
- integer value of index type refers to an operand;
- attribute which will get lowered to constant value in call;
Example:
```mlir
// Custom form defining a call to `foo()`.
%0 = emitc.call "foo" () : () -> i32
// Generic form of the same operation.
%0 = "emitc.call"() {callee = "foo"} : () -> i32
```
}];
let arguments = (ins
Arg<StrAttr, "the C++ function to call">:$callee,
Arg<OptionalAttr<ArrayAttr>, "the order of operands and further attributes">:$args,
Arg<OptionalAttr<ArrayAttr>, "template arguments">:$template_args,
Variadic<AnyType>:$operands
);
let results = (outs Variadic<AnyType>);
let assemblyFormat = [{
$callee `(` $operands `)` attr-dict `:` functional-type($operands, results)
}];
}
def EmitC_ConstantOp : EmitC_Op<"constant", [ConstantLike]> {
let summary = "Constant operation";
let description = [{
The `constant` operation produces an SSA value equal to some constant
specified by an attribute. This can be used to form simple integer and
floating point constants, as well as more exotic things like tensor
constants. The `constant` operation also supports the EmitC opaque
attribute and the EmitC opaque type.
Example:
```mlir
// Integer constant
%0 = "emitc.constant"(){value = 42 : i32} : () -> i32
// Constant emitted as `int32_t* = NULL;`
%1 = "emitc.constant"()
{value = #emitc.opaque<"NULL"> : !emitc.opaque<"int32_t*">}
: () -> !emitc.opaque<"int32_t*">
```
}];
let arguments = (ins AnyAttr:$value);
let results = (outs AnyType);
let hasFolder = 1;
}
def EmitC_IncludeOp
: EmitC_Op<"include", [NoSideEffect, HasParent<"ModuleOp">]> {
let summary = "Include operation";
let description = [{
The `include` operation allows to define a source file inclusion via the
`#include` directive.
Example:
```mlir
// Custom form defining the inclusion of `<myheader>`.
emitc.include "myheader.h" is_standard_include
// Generic form of the same operation.
"emitc.include" (){include = "myheader.h", is_standard_include} : () -> ()
// Generic form defining the inclusion of `"myheader"`.
"emitc.include" (){include = "myheader.h"} : () -> ()
```
}];
let arguments = (ins
Arg<StrAttr, "source file to include">:$include,
UnitAttr:$is_standard_include
);
let assemblyFormat = [{
$include attr-dict (`is_standard_include` $is_standard_include^)?
}];
let verifier = ?;
}
#endif // MLIR_DIALECT_EMITC_IR_EMITC

View File

@ -0,0 +1,45 @@
//===- EmitCAttributes.td - EmitC attributes ---------------*- tablegen -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Defines the MLIR EmitC attributes.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_DIALECT_EMITC_IR_EMITCATTRIBUTES
#define MLIR_DIALECT_EMITC_IR_EMITCATTRIBUTES
include "mlir/Dialect/EmitC/IR/EmitCBase.td"
//===----------------------------------------------------------------------===//
// EmitC attribute definitions
//===----------------------------------------------------------------------===//
class EmitC_Attr<string name, string attrMnemonic>
: AttrDef<EmitC_Dialect, name> {
let mnemonic = attrMnemonic;
}
def EmitC_OpaqueAttr : EmitC_Attr<"Opaque", "opaque"> {
let summary = "An opaque attribute";
let description = [{
An opaque attribute of which the value gets emitted as is.
Example:
```mlir
#emitc.opaque<"">
#emitc.opaque<"NULL">
#emitc.opaque<"nullptr">
```
}];
let parameters = (ins StringRefParameter<"the opaque value">:$value);
}
#endif // MLIR_DIALECT_EMITC_IR_EMITCATTRIBUTES

View File

@ -0,0 +1,28 @@
//===- EmitCBase.td - EmitC dialect ------------------------*- tablegen -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Defines the MLIR EmitC dialect.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_DIALECT_EMITC_IR_EMITCBASE
#define MLIR_DIALECT_EMITC_IR_EMITCBASE
include "mlir/IR/OpBase.td"
//===----------------------------------------------------------------------===//
// EmitC dialect definition
//===----------------------------------------------------------------------===//
def EmitC_Dialect : Dialect {
let name = "emitc";
let cppNamespace = "::mlir::emitc";
let hasConstantMaterializer = 1;
}
#endif // MLIR_DIALECT_EMITC_IR_EMITCBASE

View File

@ -0,0 +1,46 @@
//===- EmitCTypes.td - EmitC types -------------------------*- tablegen -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Defines the MLIR EmitC types.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_DIALECT_EMITC_IR_EMITCTYPES
#define MLIR_DIALECT_EMITC_IR_EMITCTYPES
include "mlir/Dialect/EmitC/IR/EmitCBase.td"
//===----------------------------------------------------------------------===//
// EmitC type definitions
//===----------------------------------------------------------------------===//
class EmitC_Type<string name, string typeMnemonic>
: TypeDef<EmitC_Dialect, name> {
let mnemonic = typeMnemonic;
}
def EmitC_OpaqueType : EmitC_Type<"Opaque", "opaque"> {
let summary = "An opaque type";
let description = [{
An opaque data type of which the value gets emitted as is.
Example:
```mlir
!emitc.opaque<"int">
!emitc.opaque<"float *">
!emitc.opaque<"std::vector<std::string>">
```
}];
let parameters = (ins StringRefParameter<"the opaque value">:$value);
}
#endif // MLIR_DIALECT_EMITC_IR_EMITCTYPES

View File

@ -21,6 +21,7 @@
#include "mlir/Dialect/Async/IR/Async.h"
#include "mlir/Dialect/Complex/IR/Complex.h"
#include "mlir/Dialect/DLTI/DLTI.h"
#include "mlir/Dialect/EmitC/IR/EmitC.h"
#include "mlir/Dialect/GPU/GPUDialect.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/LLVMIR/NVVMDialect.h"
@ -57,6 +58,7 @@ inline void registerAllDialects(DialectRegistry &registry) {
async::AsyncDialect,
complex::ComplexDialect,
DLTIDialect,
emitc::EmitCDialect,
gpu::GPUDialect,
LLVM::LLVMDialect,
linalg::LinalgDialect,

View File

@ -5,6 +5,7 @@ add_subdirectory(Async)
add_subdirectory(AMX)
add_subdirectory(Complex)
add_subdirectory(DLTI)
add_subdirectory(EmitC)
add_subdirectory(GPU)
add_subdirectory(Linalg)
add_subdirectory(LLVMIR)

View File

@ -0,0 +1 @@
add_subdirectory(IR)

View File

@ -0,0 +1,14 @@
add_mlir_dialect_library(MLIREmitC
EmitC.cpp
ADDITIONAL_HEADER_DIRS
${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/EmitC
DEPENDS
MLIREmitCIncGen
MLIREmitCAttributesIncGen
LINK_LIBS PUBLIC
MLIRIR
MLIRSideEffectInterfaces
)

View File

@ -0,0 +1,212 @@
//===- EmitC.cpp - EmitC Dialect ------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/EmitC/IR/EmitC.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/DialectImplementation.h"
#include "llvm/ADT/TypeSwitch.h"
using namespace mlir;
using namespace mlir::emitc;
//===----------------------------------------------------------------------===//
// EmitCDialect
//===----------------------------------------------------------------------===//
void EmitCDialect::initialize() {
addOperations<
#define GET_OP_LIST
#include "mlir/Dialect/EmitC/IR/EmitC.cpp.inc"
>();
addTypes<
#define GET_TYPEDEF_LIST
#include "mlir/Dialect/EmitC/IR/EmitCTypes.cpp.inc"
>();
addAttributes<
#define GET_ATTRDEF_LIST
#include "mlir/Dialect/EmitC/IR/EmitCAttributes.cpp.inc"
>();
}
/// Materialize a single constant operation from a given attribute value with
/// the desired resultant type.
Operation *EmitCDialect::materializeConstant(OpBuilder &builder,
Attribute value, Type type,
Location loc) {
return builder.create<ConstantOp>(loc, type, value);
}
//===----------------------------------------------------------------------===//
// ApplyOp
//===----------------------------------------------------------------------===//
static LogicalResult verify(ApplyOp op) {
StringRef applicableOperator = op.applicableOperator();
// Applicable operator must not be empty.
if (applicableOperator.empty())
return op.emitOpError("applicable operator must not be empty");
// Only `*` and `&` are supported.
if (applicableOperator != "&" && applicableOperator != "*")
return op.emitOpError("applicable operator is illegal");
return success();
}
//===----------------------------------------------------------------------===//
// CallOp
//===----------------------------------------------------------------------===//
static LogicalResult verify(emitc::CallOp op) {
// Callee must not be empty.
if (op.callee().empty())
return op.emitOpError("callee must not be empty");
if (Optional<ArrayAttr> argsAttr = op.args()) {
for (Attribute arg : argsAttr.getValue()) {
if (arg.getType().isa<IndexType>()) {
int64_t index = arg.cast<IntegerAttr>().getInt();
// Args with elements of type index must be in range
// [0..operands.size).
if ((index < 0) || (index >= static_cast<int64_t>(op.getNumOperands())))
return op.emitOpError("index argument is out of range");
// Args with elements of type ArrayAttr must have a type.
} else if (arg.isa<ArrayAttr>() && arg.getType().isa<NoneType>()) {
return op.emitOpError("array argument has no type");
}
}
}
if (Optional<ArrayAttr> templateArgsAttr = op.template_args()) {
for (Attribute tArg : templateArgsAttr.getValue()) {
if (!tArg.isa<TypeAttr>() && !tArg.isa<IntegerAttr>() &&
!tArg.isa<FloatAttr>() && !tArg.isa<emitc::OpaqueAttr>())
return op.emitOpError("template argument has invalid type");
}
}
return success();
}
//===----------------------------------------------------------------------===//
// ConstantOp
//===----------------------------------------------------------------------===//
/// The constant op requires that the attribute's type matches the return type.
static LogicalResult verify(emitc::ConstantOp &op) {
Attribute value = op.value();
Type type = op.getType();
if (!value.getType().isa<NoneType>() && type != value.getType())
return op.emitOpError() << "requires attribute's type (" << value.getType()
<< ") to match op's return type (" << type << ")";
return success();
}
OpFoldResult emitc::ConstantOp::fold(ArrayRef<Attribute> operands) {
assert(operands.empty() && "constant has no operands");
return value();
}
//===----------------------------------------------------------------------===//
// TableGen'd op method definitions
//===----------------------------------------------------------------------===//
#define GET_OP_CLASSES
#include "mlir/Dialect/EmitC/IR/EmitC.cpp.inc"
//===----------------------------------------------------------------------===//
// EmitC Attributes
//===----------------------------------------------------------------------===//
#define GET_ATTRDEF_CLASSES
#include "mlir/Dialect/EmitC/IR/EmitCAttributes.cpp.inc"
Attribute emitc::OpaqueAttr::parse(MLIRContext *context,
DialectAsmParser &parser, Type type) {
if (parser.parseLess())
return Attribute();
StringRef value;
llvm::SMLoc loc = parser.getCurrentLocation();
if (parser.parseOptionalString(&value)) {
parser.emitError(loc) << "expected string";
return Attribute();
}
if (parser.parseGreater())
return Attribute();
return get(context, value);
}
Attribute EmitCDialect::parseAttribute(DialectAsmParser &parser,
Type type) const {
llvm::SMLoc typeLoc = parser.getCurrentLocation();
StringRef mnemonic;
if (parser.parseKeyword(&mnemonic))
return Attribute();
Attribute genAttr;
OptionalParseResult parseResult =
generatedAttributeParser(getContext(), parser, mnemonic, type, genAttr);
if (parseResult.hasValue())
return genAttr;
parser.emitError(typeLoc, "unknown attribute in EmitC dialect");
return Attribute();
}
void EmitCDialect::printAttribute(Attribute attr, DialectAsmPrinter &os) const {
if (failed(generatedAttributePrinter(attr, os)))
llvm_unreachable("unexpected 'EmitC' attribute kind");
}
void emitc::OpaqueAttr::print(DialectAsmPrinter &printer) const {
printer << "opaque<\"" << getValue() << "\">";
}
//===----------------------------------------------------------------------===//
// EmitC Types
//===----------------------------------------------------------------------===//
#define GET_TYPEDEF_CLASSES
#include "mlir/Dialect/EmitC/IR/EmitCTypes.cpp.inc"
Type emitc::OpaqueType::parse(MLIRContext *context, DialectAsmParser &parser) {
if (parser.parseLess())
return Type();
StringRef value;
llvm::SMLoc loc = parser.getCurrentLocation();
if (parser.parseOptionalString(&value) || value.empty()) {
parser.emitError(loc) << "expected non empty string";
return Type();
}
if (parser.parseGreater())
return Type();
return get(context, value);
}
Type EmitCDialect::parseType(DialectAsmParser &parser) const {
llvm::SMLoc typeLoc = parser.getCurrentLocation();
StringRef mnemonic;
if (parser.parseKeyword(&mnemonic))
return Type();
Type genType;
OptionalParseResult parseResult =
generatedTypeParser(getContext(), parser, mnemonic, genType);
if (parseResult.hasValue())
return genType;
parser.emitError(typeLoc, "unknown type in EmitC dialect");
return Type();
}
void EmitCDialect::printType(Type type, DialectAsmPrinter &os) const {
if (failed(generatedTypePrinter(type, os)))
llvm_unreachable("unexpected 'EmitC' type kind");
}
void emitc::OpaqueType::print(DialectAsmPrinter &printer) const {
printer << "opaque<\"" << getValue() << "\">";
}

View File

@ -0,0 +1,79 @@
// RUN: mlir-opt %s -split-input-file -verify-diagnostics
func @const_attribute_return_type_1() {
// expected-error @+1 {{'emitc.constant' op requires attribute's type ('i64') to match op's return type ('i32')}}
%c0 = "emitc.constant"(){value = 42: i64} : () -> i32
return
}
// -----
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">
return
}
// -----
func @index_args_out_of_range_1() {
// expected-error @+1 {{'emitc.call' op index argument is out of range}}
emitc.call "test" () {args = [0 : index]} : () -> ()
return
}
// -----
func @index_args_out_of_range_2(%arg : i32) {
// expected-error @+1 {{'emitc.call' op index argument is out of range}}
emitc.call "test" (%arg, %arg) {args = [2 : index]} : (i32, i32) -> ()
return
}
// -----
func @empty_callee() {
// expected-error @+1 {{'emitc.call' op callee must not be empty}}
emitc.call "" () : () -> ()
return
}
// -----
func @nonetype_arg(%arg : i32) {
// expected-error @+1 {{'emitc.call' op array argument has no type}}
emitc.call "nonetype_arg"(%arg) {args = [0 : index, [0, 1, 2]]} : (i32) -> i32
return
}
// -----
func @array_template_arg(%arg : i32) {
// expected-error @+1 {{'emitc.call' op template argument has invalid type}}
emitc.call "nonetype_template_arg"(%arg) {template_args = [[0, 1, 2]]} : (i32) -> i32
return
}
// -----
func @dense_template_argument(%arg : i32) {
// expected-error @+1 {{'emitc.call' op template argument has invalid type}}
emitc.call "dense_template_argument"(%arg) {template_args = [dense<[1.0, 1.0]> : tensor<2xf32>]} : (i32) -> i32
return
}
// -----
func @empty_operator(%arg : i32) {
// expected-error @+1 {{'emitc.apply' op applicable operator must not be empty}}
%2 = emitc.apply ""(%arg) : (i32) -> !emitc.opaque<"int32_t*">
return
}
// -----
func @illegal_operator(%arg : i32) {
// expected-error @+1 {{'emitc.apply' op applicable operator is illegal}}
%2 = emitc.apply "+"(%arg) : (i32) -> !emitc.opaque<"int32_t*">
return
}

View File

@ -0,0 +1,24 @@
// RUN: mlir-opt -verify-diagnostics %s | FileCheck %s
"emitc.include" (){include = "test.h", is_standard_include} : () -> ()
emitc.include "test.h" is_standard_include
// CHECK-LABEL: func @f(%{{.*}}: i32, %{{.*}}: !emitc.opaque<"int32_t">) {
func @f(%arg0: i32, %f: !emitc.opaque<"int32_t">) {
%1 = "emitc.call"() {callee = "blah"} : () -> i64
emitc.call "foo" (%1) {args = [
0 : index, dense<[0, 1]> : tensor<2xi32>, 0 : index
]} : (i64) -> ()
return
}
func @c(%arg0: i32) {
%1 = "emitc.constant"(){value = 42 : i32} : () -> i32
return
}
func @a(%arg0: i32, %arg1: i32) {
%1 = "emitc.apply"(%arg0) {applicableOperator = "&"} : (i32) -> !emitc.opaque<"int32_t*">
%2 = emitc.apply "&"(%arg1) : (i32) -> !emitc.opaque<"int32_t*">
return
}

View File

@ -0,0 +1,18 @@
// RUN: mlir-opt -verify-diagnostics %s | FileCheck %s
// check parser
// RUN: mlir-opt -verify-diagnostics %s | mlir-opt -verify-diagnostics | FileCheck %s
// CHECK-LABEL: func @opaque_types() {
func @opaque_types() {
// CHECK-NEXT: !emitc.opaque<"int">
emitc.call "f"() {args = [!emitc<"opaque<\"int\">">]} : () -> ()
// CHECK-NEXT: !emitc.opaque<"byte">
emitc.call "f"() {args = [!emitc<"opaque<\"byte\">">]} : () -> ()
// CHECK-NEXT: !emitc.opaque<"unsigned">
emitc.call "f"() {args = [!emitc<"opaque<\"unsigned\">">]} : () -> ()
// CHECK-NEXT: !emitc.opaque<"status_t">
emitc.call "f"() {args = [!emitc<"opaque<\"status_t\">">]} : () -> ()
// CHECK-NEXT: !emitc.opaque<"std::vector<std::string>">
emitc.call "f"() {args = [!emitc.opaque<"std::vector<std::string>">]} : () -> ()
return
}

View File

@ -8,6 +8,7 @@
// CHECK-NEXT: async
// CHECK-NEXT: complex
// CHECK-NEXT: dlti
// CHECK-NEXT: emitc
// CHECK-NEXT: gpu
// CHECK-NEXT: linalg
// CHECK-NEXT: llvm