From 818af71b72219d149174faf9420dfc00f2c03470 Mon Sep 17 00:00:00 2001 From: Matthias Gehre Date: Mon, 11 Mar 2024 16:40:57 +0100 Subject: [PATCH] [mlir][emitc] Add ArrayType (#83386) This models a one or multi-dimensional C/C++ array. The type implements the `ShapedTypeInterface` and prints similar to memref/tensor: ``` %arg0: !emitc.array<1xf32>, %arg1: !emitc.array<10x20x30xi32>, %arg2: !emitc.array<30x!emitc.ptr>, %arg3: !emitc.array<30x!emitc.opaque<"int">> ``` It can be translated to a C array type when used as function parameter or as `emitc.variable` type. --- .../mlir/Dialect/EmitC/IR/EmitCTypes.td | 52 ++++++++++++- mlir/lib/Dialect/EmitC/IR/EmitC.cpp | 73 +++++++++++++++++++ mlir/lib/Target/Cpp/TranslateToCpp.cpp | 54 ++++++++++++-- mlir/test/Dialect/EmitC/invalid_ops.mlir | 32 ++++++++ mlir/test/Dialect/EmitC/invalid_types.mlir | 70 ++++++++++++++++++ mlir/test/Dialect/EmitC/types.mlir | 14 ++++ mlir/test/Target/Cpp/common-cpp.mlir | 5 ++ mlir/test/Target/Cpp/declare_func.mlir | 8 ++ mlir/test/Target/Cpp/func.mlir | 3 + mlir/test/Target/Cpp/invalid.mlir | 28 +++++++ .../Cpp/invalid_declare_variables_at_top.mlir | 9 +++ mlir/test/Target/Cpp/variable.mlir | 6 ++ 12 files changed, 346 insertions(+), 8 deletions(-) create mode 100644 mlir/test/Target/Cpp/invalid_declare_variables_at_top.mlir diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td index 8818c049ed7713..1ff41022eba8c9 100644 --- a/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td +++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td @@ -16,16 +16,64 @@ include "mlir/IR/AttrTypeBase.td" include "mlir/Dialect/EmitC/IR/EmitCBase.td" +include "mlir/IR/BuiltinTypeInterfaces.td" //===----------------------------------------------------------------------===// // EmitC type definitions //===----------------------------------------------------------------------===// -class EmitC_Type - : TypeDef { +class EmitC_Type traits = []> + : TypeDef { let mnemonic = typeMnemonic; } +def EmitC_ArrayType : EmitC_Type<"Array", "array", [ShapedTypeInterface]> { + let summary = "EmitC array type"; + + let description = [{ + An array data type. + + Example: + + ```mlir + // Array emitted as `int32_t[10]` + !emitc.array<10xi32> + // Array emitted as `float[10][20]` + !emitc.ptr<10x20xf32> + ``` + }]; + + let parameters = (ins + ArrayRefParameter<"int64_t">:$shape, + "Type":$elementType + ); + + let builders = [ + TypeBuilderWithInferredContext<(ins + "ArrayRef":$shape, + "Type":$elementType + ), [{ + return $_get(elementType.getContext(), shape, elementType); + }]> + ]; + let extraClassDeclaration = [{ + /// Returns if this type is ranked (always true). + bool hasRank() const { return true; } + + /// Clone this array type with the given shape and element type. If the + /// provided shape is `std::nullopt`, the current shape of the type is used. + ArrayType cloneWith(std::optional> shape, + Type elementType) const; + + static bool isValidElementType(Type type) { + return type.isIntOrIndexOrFloat() || + llvm::isa(type); + } + }]; + let genVerifyDecl = 1; + let hasCustomAssemblyFormat = 1; +} + def EmitC_OpaqueType : EmitC_Type<"Opaque", "opaque"> { let summary = "EmitC opaque type"; diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp index 07ee1d394287b9..9426bbbe2370f0 100644 --- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp +++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp @@ -141,6 +141,8 @@ LogicalResult emitc::AssignOp::verify() { return emitOpError() << "requires value's type (" << value.getType() << ") to match variable's type (" << variable.getType() << ")"; + if (isa(variable.getType())) + return emitOpError() << "cannot assign to array type"; return success(); } @@ -192,6 +194,11 @@ LogicalResult emitc::CallOpaqueOp::verify() { } } + if (llvm::any_of(getResultTypes(), + [](Type type) { return isa(type); })) { + return emitOpError() << "cannot return array type"; + } + return success(); } @@ -456,6 +463,9 @@ LogicalResult FuncOp::verify() { return emitOpError("requires zero or exactly one result, but has ") << getNumResults(); + if (getNumResults() == 1 && isa(getResultTypes()[0])) + return emitOpError("cannot return array type"); + return success(); } @@ -763,6 +773,69 @@ LogicalResult emitc::YieldOp::verify() { #define GET_TYPEDEF_CLASSES #include "mlir/Dialect/EmitC/IR/EmitCTypes.cpp.inc" +//===----------------------------------------------------------------------===// +// ArrayType +//===----------------------------------------------------------------------===// + +Type emitc::ArrayType::parse(AsmParser &parser) { + if (parser.parseLess()) + return Type(); + + SmallVector dimensions; + if (parser.parseDimensionList(dimensions, /*allowDynamic=*/false, + /*withTrailingX=*/true)) + return Type(); + // Parse the element type. + auto typeLoc = parser.getCurrentLocation(); + Type elementType; + if (parser.parseType(elementType)) + return Type(); + + // Check that array is formed from allowed types. + if (!isValidElementType(elementType)) + return parser.emitError(typeLoc, "invalid array element type"), Type(); + if (parser.parseGreater()) + return Type(); + return parser.getChecked(dimensions, elementType); +} + +void emitc::ArrayType::print(AsmPrinter &printer) const { + printer << "<"; + for (int64_t dim : getShape()) { + printer << dim << 'x'; + } + printer.printType(getElementType()); + printer << ">"; +} + +LogicalResult emitc::ArrayType::verify( + ::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError, + ::llvm::ArrayRef shape, Type elementType) { + if (shape.empty()) + return emitError() << "shape must not be empty"; + + for (int64_t dim : shape) { + if (dim <= 0) + return emitError() << "dimensions must have positive size"; + } + + if (!elementType) + return emitError() << "element type must not be none"; + + if (!isValidElementType(elementType)) + return emitError() << "invalid array element type"; + + return success(); +} + +emitc::ArrayType +emitc::ArrayType::cloneWith(std::optional> shape, + Type elementType) const { + if (!shape) + return emitc::ArrayType::get(getShape(), elementType); + return emitc::ArrayType::get(*shape, elementType); +} + //===----------------------------------------------------------------------===// // OpaqueType //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp index 3d71b4a3e315e7..3cf137c1d07c0e 100644 --- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp +++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp @@ -139,6 +139,10 @@ struct CppEmitter { LogicalResult emitVariableDeclaration(OpResult result, bool trailingSemicolon); + /// Emits a declaration of a variable with the given type and name. + LogicalResult emitVariableDeclaration(Location loc, Type type, + StringRef name); + /// Emits the variable declaration and assignment prefix for 'op'. /// - emits separate variable followed by std::tie for multi-valued operation; /// - emits single type followed by variable for single result; @@ -870,10 +874,8 @@ static LogicalResult printFunctionArgs(CppEmitter &emitter, return (interleaveCommaWithError( arguments, os, [&](BlockArgument arg) -> LogicalResult { - if (failed(emitter.emitType(functionOp->getLoc(), arg.getType()))) - return failure(); - os << " " << emitter.getOrCreateName(arg); - return success(); + return emitter.emitVariableDeclaration( + functionOp->getLoc(), arg.getType(), emitter.getOrCreateName(arg)); })); } @@ -917,6 +919,9 @@ static LogicalResult printFunctionBody(CppEmitter &emitter, if (emitter.hasValueInScope(arg)) return functionOp->emitOpError(" block argument #") << arg.getArgNumber() << " is out of scope"; + if (isa(arg.getType())) + return functionOp->emitOpError("cannot emit block argument #") + << arg.getArgNumber() << " with array type"; if (failed( emitter.emitType(block.getParentOp()->getLoc(), arg.getType()))) { return failure(); @@ -960,6 +965,11 @@ static LogicalResult printOperation(CppEmitter &emitter, "with multiple blocks needs variables declared at top"); } + if (llvm::any_of(functionOp.getResultTypes(), + [](Type type) { return isa(type); })) { + return functionOp.emitOpError() << "cannot emit array type as result type"; + } + CppEmitter::Scope scope(emitter); raw_indented_ostream &os = emitter.ostream(); if (failed(emitter.emitTypes(functionOp.getLoc(), @@ -1306,9 +1316,10 @@ LogicalResult CppEmitter::emitVariableDeclaration(OpResult result, return result.getDefiningOp()->emitError( "result variable for the operation already declared"); } - if (failed(emitType(result.getOwner()->getLoc(), result.getType()))) + if (failed(emitVariableDeclaration(result.getOwner()->getLoc(), + result.getType(), + getOrCreateName(result)))) return failure(); - os << " " << getOrCreateName(result); if (trailingSemicolon) os << ";\n"; return success(); @@ -1403,6 +1414,23 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) { return success(); } +LogicalResult CppEmitter::emitVariableDeclaration(Location loc, Type type, + StringRef name) { + if (auto arrType = dyn_cast(type)) { + if (failed(emitType(loc, arrType.getElementType()))) + return failure(); + os << " " << name; + for (auto dim : arrType.getShape()) { + os << "[" << dim << "]"; + } + return success(); + } + if (failed(emitType(loc, type))) + return failure(); + os << " " << name; + return success(); +} + LogicalResult CppEmitter::emitType(Location loc, Type type) { if (auto iType = dyn_cast(type)) { switch (iType.getWidth()) { @@ -1438,6 +1466,8 @@ LogicalResult CppEmitter::emitType(Location loc, Type type) { if (!tType.hasStaticShape()) return emitError(loc, "cannot emit tensor type with non static shape"); os << "Tensor<"; + if (isa(tType.getElementType())) + return emitError(loc, "cannot emit tensor of array type ") << type; if (failed(emitType(loc, tType.getElementType()))) return failure(); auto shape = tType.getShape(); @@ -1454,7 +1484,16 @@ LogicalResult CppEmitter::emitType(Location loc, Type type) { os << oType.getValue(); return success(); } + if (auto aType = dyn_cast(type)) { + if (failed(emitType(loc, aType.getElementType()))) + return failure(); + for (auto dim : aType.getShape()) + os << "[" << dim << "]"; + return success(); + } if (auto pType = dyn_cast(type)) { + if (isa(pType.getPointee())) + return emitError(loc, "cannot emit pointer to array type ") << type; if (failed(emitType(loc, pType.getPointee()))) return failure(); os << "*"; @@ -1476,6 +1515,9 @@ LogicalResult CppEmitter::emitTypes(Location loc, ArrayRef types) { } LogicalResult CppEmitter::emitTupleType(Location loc, ArrayRef types) { + if (llvm::any_of(types, [](Type type) { return isa(type); })) { + return emitError(loc, "cannot emit tuple of array type"); + } os << "std::tuple<"; if (failed(interleaveCommaWithError( types, os, [&](Type type) { return emitType(loc, type); }))) diff --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir index 5f64b535d684f3..58b3a11ed93e15 100644 --- a/mlir/test/Dialect/EmitC/invalid_ops.mlir +++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir @@ -80,6 +80,14 @@ func.func @dense_template_argument(%arg : i32) { // ----- +func.func @array_result() { + // expected-error @+1 {{'emitc.call_opaque' op cannot return array type}} + emitc.call_opaque "array_result"() : () -> !emitc.array<4xi32> + return +} + +// ----- + func.func @empty_operator(%arg : i32) { // expected-error @+1 {{'emitc.apply' op applicable operator must not be empty}} %2 = emitc.apply ""(%arg) : (i32) -> !emitc.ptr @@ -129,6 +137,14 @@ func.func @cast_tensor(%arg : tensor) { // ----- +func.func @cast_array(%arg : !emitc.array<4xf32>) { + // expected-error @+1 {{'emitc.cast' op operand type '!emitc.array<4xf32>' and result type '!emitc.array<4xf32>' are cast incompatible}} + %1 = emitc.cast %arg: !emitc.array<4xf32> to !emitc.array<4xf32> + return +} + +// ----- + func.func @add_two_pointers(%arg0: !emitc.ptr, %arg1: !emitc.ptr) { // expected-error @+1 {{'emitc.add' op requires that at most one operand is a pointer}} %1 = "emitc.add" (%arg0, %arg1) : (!emitc.ptr, !emitc.ptr) -> !emitc.ptr @@ -235,6 +251,15 @@ func.func @test_assign_type_mismatch(%arg1: f32) { // ----- +func.func @test_assign_to_array(%arg1: !emitc.array<4xi32>) { + %v = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.array<4xi32> + // expected-error @+1 {{'emitc.assign' op cannot assign to array type}} + emitc.assign %arg1 : !emitc.array<4xi32> to %v : !emitc.array<4xi32> + return +} + +// ----- + func.func @test_expression_no_yield() -> i32 { // expected-error @+1 {{'emitc.expression' op must yield a value at termination}} %r = emitc.expression : i32 { @@ -313,6 +338,13 @@ emitc.func @return_type_mismatch() -> i32 { // ----- +// expected-error@+1 {{'emitc.func' op cannot return array type}} +emitc.func @return_type_array(%arg : !emitc.array<4xi32>) -> !emitc.array<4xi32> { + emitc.return %arg : !emitc.array<4xi32> +} + +// ----- + func.func @return_inside_func.func(%0: i32) -> (i32) { // expected-error@+1 {{'emitc.return' op expects parent op 'emitc.func'}} emitc.return %0 : i32 diff --git a/mlir/test/Dialect/EmitC/invalid_types.mlir b/mlir/test/Dialect/EmitC/invalid_types.mlir index 54e3775ddb8ed1..079371b39b9d1e 100644 --- a/mlir/test/Dialect/EmitC/invalid_types.mlir +++ b/mlir/test/Dialect/EmitC/invalid_types.mlir @@ -11,3 +11,73 @@ func.func @illegal_opaque_type_2() { // expected-error @+1 {{pointer not allowed as outer type with !emitc.opaque, use !emitc.ptr instead}} %1 = "emitc.variable"(){value = "nullptr" : !emitc.opaque<"int32_t*">} : () -> !emitc.opaque<"int32_t*"> } + +// ----- + +func.func @illegal_array_missing_spec( + // expected-error @+1 {{expected non-function type}} + %arg0: !emitc.array<>) { +} + +// ----- + +func.func @illegal_array_missing_shape( + // expected-error @+1 {{shape must not be empty}} + %arg9: !emitc.array) { +} + +// ----- + +func.func @illegal_array_missing_x( + // expected-error @+1 {{expected 'x' in dimension list}} + %arg0: !emitc.array<10> +) { +} + +// ----- + +func.func @illegal_array_non_positive_dimenson( + // expected-error @+1 {{dimensions must have positive size}} + %arg0: !emitc.array<0xi32> +) { +} + +// ----- + +func.func @illegal_array_missing_type( + // expected-error @+1 {{expected non-function type}} + %arg0: !emitc.array<10x> +) { +} + +// ----- + +func.func @illegal_array_dynamic_shape( + // expected-error @+1 {{expected static shape}} + %arg0: !emitc.array<10x?xi32> +) { +} + +// ----- + +func.func @illegal_array_unranked( + // expected-error @+1 {{expected non-function type}} + %arg0: !emitc.array<*xi32> +) { +} + +// ----- + +func.func @illegal_array_with_array_element_type( + // expected-error @+1 {{invalid array element type}} + %arg0: !emitc.array<4x!emitc.array<4xi32>> +) { +} + +// ----- + +func.func @illegal_array_with_tensor_element_type( + // expected-error @+1 {{invalid array element type}} + %arg0: !emitc.array<4xtensor<4xi32>> +) { +} diff --git a/mlir/test/Dialect/EmitC/types.mlir b/mlir/test/Dialect/EmitC/types.mlir index 26d6f43a5824e8..752f2c10c17be9 100644 --- a/mlir/test/Dialect/EmitC/types.mlir +++ b/mlir/test/Dialect/EmitC/types.mlir @@ -2,6 +2,20 @@ // check parser // RUN: mlir-opt -verify-diagnostics %s | mlir-opt -verify-diagnostics | FileCheck %s +// CHECK-LABEL: func @array_types( +func.func @array_types( + // CHECK-SAME: !emitc.array<1xf32>, + %arg0: !emitc.array<1xf32>, + // CHECK-SAME: !emitc.array<10x20x30xi32>, + %arg1: !emitc.array<10x20x30xi32>, + // CHECK-SAME: !emitc.array<30x!emitc.ptr>, + %arg2: !emitc.array<30x!emitc.ptr>, + // CHECK-SAME: !emitc.array<30x!emitc.opaque<"int">> + %arg3: !emitc.array<30x!emitc.opaque<"int">> +) { + return +} + // CHECK-LABEL: func @opaque_types() { func.func @opaque_types() { // CHECK-NEXT: !emitc.opaque<"int"> diff --git a/mlir/test/Target/Cpp/common-cpp.mlir b/mlir/test/Target/Cpp/common-cpp.mlir index b537e7098deb51..a87b33a10844d3 100644 --- a/mlir/test/Target/Cpp/common-cpp.mlir +++ b/mlir/test/Target/Cpp/common-cpp.mlir @@ -89,3 +89,8 @@ func.func @apply(%arg0: i32) -> !emitc.ptr { %1 = emitc.apply "*"(%0) : (!emitc.ptr) -> (i32) return %0 : !emitc.ptr } + +// CHECK: void array_type(int32_t v1[3], float v2[10][20]) +func.func @array_type(%arg0: !emitc.array<3xi32>, %arg1: !emitc.array<10x20xf32>) { + return +} diff --git a/mlir/test/Target/Cpp/declare_func.mlir b/mlir/test/Target/Cpp/declare_func.mlir index 72c087a3388e20..00680d71824ae0 100644 --- a/mlir/test/Target/Cpp/declare_func.mlir +++ b/mlir/test/Target/Cpp/declare_func.mlir @@ -14,3 +14,11 @@ emitc.declare_func @foo emitc.func @foo(%arg0: i32) -> i32 attributes {specifiers = ["static","inline"]} { emitc.return %arg0 : i32 } + + +// CHECK: void array_arg(int32_t [[V2:[^ ]*]][3]); +emitc.declare_func @array_arg +// CHECK: void array_arg(int32_t [[V2:[^ ]*]][3]) { +emitc.func @array_arg(%arg0: !emitc.array<3xi32>) { + emitc.return +} diff --git a/mlir/test/Target/Cpp/func.mlir b/mlir/test/Target/Cpp/func.mlir index a639cae6f623c5..9c9ea55bfc4e1a 100644 --- a/mlir/test/Target/Cpp/func.mlir +++ b/mlir/test/Target/Cpp/func.mlir @@ -40,3 +40,6 @@ emitc.func @emitc_call() -> i32 { emitc.func private @extern_func(i32) attributes {specifiers = ["extern"]} // CPP-DEFAULT: extern void extern_func(int32_t); + +emitc.func private @array_arg(!emitc.array<3xi32>) attributes {specifiers = ["extern"]} +// CPP-DEFAULT: extern void array_arg(int32_t[3]); diff --git a/mlir/test/Target/Cpp/invalid.mlir b/mlir/test/Target/Cpp/invalid.mlir index 18dabb91558663..552c04a9b07f7d 100644 --- a/mlir/test/Target/Cpp/invalid.mlir +++ b/mlir/test/Target/Cpp/invalid.mlir @@ -57,3 +57,31 @@ func.func @non_static_shape(%arg0 : tensor) { func.func @unranked_tensor(%arg0 : tensor<*xf32>) { return } + +// ----- + +// expected-error@+1 {{cannot emit tensor of array type}} +func.func @tensor_of_array(%arg0 : tensor<4x!emitc.array<4xf32>>) { + return +} + +// ----- + +// expected-error@+1 {{cannot emit pointer to array type}} +func.func @pointer_to_array(%arg0 : !emitc.ptr>) { + return +} + +// ----- + +// expected-error@+1 {{cannot emit array type as result type}} +func.func @array_as_result(%arg: !emitc.array<4xi8>) -> (!emitc.array<4xi8>) { + return %arg : !emitc.array<4xi8> +} + +// ----- +func.func @ptr_to_array() { + // expected-error@+1 {{cannot emit pointer to array type '!emitc.ptr>'}} + %v = "emitc.variable"(){value = #emitc.opaque<"NULL">} : () -> !emitc.ptr> + return +} diff --git a/mlir/test/Target/Cpp/invalid_declare_variables_at_top.mlir b/mlir/test/Target/Cpp/invalid_declare_variables_at_top.mlir new file mode 100644 index 00000000000000..844fe03bad4aba --- /dev/null +++ b/mlir/test/Target/Cpp/invalid_declare_variables_at_top.mlir @@ -0,0 +1,9 @@ +// RUN: mlir-translate -split-input-file -declare-variables-at-top -mlir-to-cpp -verify-diagnostics %s + +// expected-error@+1 {{'func.func' op cannot emit block argument #0 with array type}} +func.func @array_as_block_argument(!emitc.array<4xi8>) { +^bb0(%arg0 : !emitc.array<4xi8>): + cf.br ^bb1(%arg0 : !emitc.array<4xi8>) +^bb1(%a : !emitc.array<4xi8>): + return +} diff --git a/mlir/test/Target/Cpp/variable.mlir b/mlir/test/Target/Cpp/variable.mlir index 77a060a32f9d45..126dd384b47a2a 100644 --- a/mlir/test/Target/Cpp/variable.mlir +++ b/mlir/test/Target/Cpp/variable.mlir @@ -9,6 +9,8 @@ func.func @emitc_variable() { %c4 = "emitc.variable"(){value = 255 : ui8} : () -> ui8 %c5 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.ptr %c6 = "emitc.variable"(){value = #emitc.opaque<"NULL">} : () -> !emitc.ptr + %c7 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.array<3x7xi32> + %c8 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.array<5x!emitc.ptr> return } // CPP-DEFAULT: void emitc_variable() { @@ -19,6 +21,8 @@ func.func @emitc_variable() { // CPP-DEFAULT-NEXT: uint8_t [[V4:[^ ]*]] = 255; // CPP-DEFAULT-NEXT: int32_t* [[V5:[^ ]*]]; // CPP-DEFAULT-NEXT: int32_t* [[V6:[^ ]*]] = NULL; +// CPP-DEFAULT-NEXT: int32_t [[V7:[^ ]*]][3][7]; +// CPP-DEFAULT-NEXT: int8_t* [[V8:[^ ]*]][5]; // CPP-DECLTOP: void emitc_variable() { // CPP-DECLTOP-NEXT: int32_t [[V0:[^ ]*]]; @@ -28,6 +32,8 @@ func.func @emitc_variable() { // CPP-DECLTOP-NEXT: uint8_t [[V4:[^ ]*]]; // CPP-DECLTOP-NEXT: int32_t* [[V5:[^ ]*]]; // CPP-DECLTOP-NEXT: int32_t* [[V6:[^ ]*]]; +// CPP-DECLTOP-NEXT: int32_t [[V7:[^ ]*]][3][7]; +// CPP-DECLTOP-NEXT: int8_t* [[V8:[^ ]*]][5]; // CPP-DECLTOP-NEXT: ; // CPP-DECLTOP-NEXT: [[V1]] = 42; // CPP-DECLTOP-NEXT: [[V2]] = -1;