Initial version of the LLVM IR dialect

LLVM IR types are defined using MLIR's extendable type system.  The dialect
provides the only type kind, LLVMType, that wraps an llvm::Type*.  Since LLVM
IR types are pointer-unique, MLIR type systems relies on those pointers to
perform its own type unique'ing.  Type parsing and printing is delegated to
LLVM libraries.

Define MLIR operations for the LLVM IR instructions currently used by the
translation to the LLVM IR Target to simplify eventual transition.  Operations
classes are defined using TableGen.  LLVM IR instruction operands that are only
allowed to take constant values are accepted as attributes instead.  All
operations are using verbose form for printing and parsing.

PiperOrigin-RevId: 229400375
This commit is contained in:
Alex Zinenko 2019-01-15 10:53:22 -08:00 committed by jpienaar
parent 44e9869f1a
commit 0e58de70e7
5 changed files with 513 additions and 1 deletions

View File

@ -23,5 +23,6 @@
DEFINE_TYPE_KIND_RANGE(STANDARD)
DEFINE_TYPE_KIND_RANGE(TENSORFLOW_CONTROL)
DEFINE_TYPE_KIND_RANGE(TENSORFLOW)
DEFINE_TYPE_KIND_RANGE(LLVM)
#undef DEFINE_TYPE_KIND_RANGE
#undef DEFINE_TYPE_KIND_RANGE

View File

@ -0,0 +1,87 @@
//===- LLVMDialect.h - MLIR LLVM IR dialect ---------------------*- C++ -*-===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
//
// This file defines the LLVM IR dialect in MLIR, containing LLVM operations and
// LLVM type system.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_TARGET_LLVMDIALECT_H_
#define MLIR_TARGET_LLVMDIALECT_H_
#include "mlir/IR/Dialect.h"
#include "mlir/IR/OpDefinition.h"
#include "mlir/IR/OpImplementation.h"
#include "mlir/IR/TypeSupport.h"
#include "mlir/IR/Types.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Type.h"
namespace llvm {
class Type;
class LLVMContext;
} // end namespace llvm
namespace mlir {
namespace LLVM {
namespace detail {
class LLVMTypeStorage;
}
class LLVMType : public mlir::Type::TypeBase<LLVMType, mlir::Type,
detail::LLVMTypeStorage> {
public:
enum Kind {
LLVM_TYPE = FIRST_LLVM_TYPE,
};
using Base::Base;
static bool kindof(unsigned kind) { return kind == LLVM_TYPE; }
static LLVMType get(MLIRContext *context, llvm::Type *llvmType);
llvm::Type *getUnderlyingType() const;
static char typeID;
};
} // end namespace LLVM
///// Ops /////
#define GET_OP_CLASSES
#include "mlir/LLVMIR/llvm_ops.inc"
namespace LLVM {
class LLVMDialect : public Dialect {
public:
explicit LLVMDialect(MLIRContext *context);
llvm::LLVMContext &getLLVMContext() { return llvmContext; }
llvm::Module &getLLVMModule() { return module; }
private:
llvm::LLVMContext llvmContext;
llvm::Module module;
};
} // end namespace LLVM
} // end namespace mlir
#endif // MLIR_TARGET_LLVMDIALECT_H_

View File

@ -0,0 +1,188 @@
//===-- llvm_ops.td - LLVM IR dialect op definition file ---*- tablegen -*-===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
//
// This is the LLVM IR operation definition file.
//
//===----------------------------------------------------------------------===//
#ifdef LLVMIR_OPS
#else
#define LLVMIR_OPS
#ifdef OP_BASE
#else
include "mlir/IR/op_base.td"
#endif // OP_BASE
// LLVM IR type wrapped in MLIR.
def LLVM_Type : Type<CPred<"{0}.isa<::mlir::LLVM::LLVMType>()">,
"LLVM dialect type">;
// Base class for LLVM operations. All operations get an "llvm." prefix in
// their name automatically. LLVM operations have either zero or one result,
// this class is specialized below for both cases and should not be used
// directly.
class LLVM_Op<string mnemonic, list<OpProperty> props = [],
list<string> traits = []> :
Op<!strconcat("llvm.", mnemonic), props>, Traits<traits> {
// No need for custom parsing since custom printer uses the default syntax
// instead.
let parser =
[{ llvm_unreachable("custom parsing triggered instead of default"); }];
// Just use the verbose form.
let printer = [{ p->printDefaultOp(this->getInstruction()); }];
}
// Base class for LLVM operations with one result.
class LLVM_OneResultOp<string mnemonic, list<OpProperty> props = [],
list<string> traits = []> :
LLVM_Op<mnemonic, props, traits>, Results<[LLVM_Type]> {
let builder = [{
static void build(Builder *builder, OperationState *result, Type resultType,
ArrayRef<Value *> operands,
ArrayRef<NamedAttribute> attributes = {}) {
(void) builder;
result->addTypes(resultType);
result->addOperands(operands);
for (auto namedAttr : attributes) {
result->addAttribute(namedAttr.first, namedAttr.second);
}
}
}];
}
// Base class for LLVM operations with zero results.
class LLVM_ZeroResultOp<string mnemonic, list<OpProperty> props = [],
list<string> traits = []> :
LLVM_Op<mnemonic, props, traits>, Results<[]> {
let builder = [{
// Compatibility builder that takes an instance of wrapped llvm::VoidType
// to indicate no result.
static void build(Builder *builder, OperationState *result, Type resultType,
ArrayRef<Value *> operands,
ArrayRef<NamedAttribute> attributes = {}) {
auto llvmType = resultType.dyn_cast<LLVM::LLVMType>();
assert(llvmType && "result must be an LLVM type");
assert(llvmType.getUnderlyingType() &&
llvmType.getUnderlyingType()->isVoidTy() &&
"for zero-result operands, only 'void' is accepted as result type");
build(builder, result, operands, attributes);
}
// Convenience builder that does not take a return type.
static void build(Builder *builder, OperationState *result,
ArrayRef<Value *> operands,
ArrayRef<NamedAttribute> attributes = {}) {
(void) builder;
result->addOperands(operands);
for (auto namedAttr : attributes) {
result->addAttribute(namedAttr.first, namedAttr.second);
}
}
}];
}
// Base class for LLVM terminator operations. All terminator operations have
// zero results and an optional list of successors.
class LLVM_TerminatorOp<string mnemonic, list<OpProperty> props = [],
list<string> traits = []> :
LLVM_Op<mnemonic, props,
!listconcat(["IsTerminator", "VariadicOperands"], traits)>,
Results<[]> {
let builder = [{
static void build(Builder *builder, OperationState *result,
ArrayRef<Value *> properOperands,
ArrayRef<Block *> destinations,
ArrayRef<ArrayRef<Value *>> operands = {},
ArrayRef<NamedAttribute> attributes = {}) {
(void) builder;
result->addOperands(properOperands);
for (auto kvp : llvm::zip(destinations, operands)) {
result->addSuccessor(std::get<0>(kvp), std::get<1>(kvp));
}
for (auto namedAttr : attributes) {
result->addAttribute(namedAttr.first, namedAttr.second);
}
}
}];
}
// Class for arithmetic binary instructions.
class LLVM_ArithmeticOp<string mnemonic, list<OpProperty> props = [],
list<string> traits = []> :
LLVM_OneResultOp<mnemonic,
!listconcat([NoSideEffect], props),
!listconcat(["SameOperandsAndResultType"], traits)>,
Arguments<(ins LLVM_Type:$lhs, LLVM_Type:$rhs)>;
// Class for variadic instructions.
class LLVM_VariadicOneResultOp<string mnemonic, list<OpProperty> props = [],
list<string> traits = []> :
LLVM_OneResultOp<mnemonic, props,
!listconcat(["VariadicOperands"], traits)>;
// Integer binary instructions.
def LLVM_AddOp : LLVM_ArithmeticOp<"add", [Commutative]>;
def LLVM_SubOp : LLVM_ArithmeticOp<"sub">;
def LLVM_MulOp : LLVM_ArithmeticOp<"mul", [Commutative]>;
def LLVM_UDivOp : LLVM_ArithmeticOp<"udiv">;
def LLVM_SDivOp : LLVM_ArithmeticOp<"sdiv">;
def LLVM_URemOp : LLVM_ArithmeticOp<"urem">;
def LLVM_SRemOp : LLVM_ArithmeticOp<"srem">;
// Other integer instructions.
def LLVM_ICmpOp : LLVM_OneResultOp<"icmp", [NoSideEffect],
["SameOperandsAndResultShape"]>,
Arguments<(ins LLVM_Type:$lhs, LLVM_Type:$rhs)>;
// Floating point binary instructions.
def LLVM_FAddOp : LLVM_ArithmeticOp<"fadd">;
def LLVM_FSubOp : LLVM_ArithmeticOp<"fsub">;
def LLVM_FMulOp : LLVM_ArithmeticOp<"fmul">;
def LLVM_FDivOp : LLVM_ArithmeticOp<"fdiv">;
def LLVM_FRemOp : LLVM_ArithmeticOp<"frem">;
// Memory-related instructions.
def LLVM_AllocaOp : LLVM_OneResultOp<"alloca">,
Arguments<(ins LLVM_Type:$arraySize)>;
def LLVM_GEPOp : LLVM_VariadicOneResultOp<"getelementptr", [NoSideEffect]>;
def LLVM_LoadOp : LLVM_OneResultOp<"load">, Arguments<(ins LLVM_Type:$addr)>;
def LLVM_StoreOp : LLVM_ZeroResultOp<"store">,
Arguments<(ins LLVM_Type:$value, LLVM_Type:$addr)>;
def LLVM_BitcastOp : LLVM_OneResultOp<"bitcast", [NoSideEffect]>,
Arguments<(ins LLVM_Type)>;
// Call-related instructions.
def LLVM_CallOp : LLVM_VariadicOneResultOp<"call">;
def LLVM_ExtractValueOp : LLVM_OneResultOp<"extractvalue", [NoSideEffect]>,
Arguments<(ins LLVM_Type)>;
def LLVM_InsertValueOp : LLVM_OneResultOp<"insertvalue", [NoSideEffect]>,
Arguments<(ins LLVM_Type, LLVM_Type)>;
// Terminators.
def LLVM_BrOp : LLVM_TerminatorOp<"br", [NoSideEffect]>;
def LLVM_CondBrOp : LLVM_TerminatorOp<"cond_br", [NoSideEffect]>;
def LLVM_ReturnOp : LLVM_TerminatorOp<"return", [NoSideEffect]>;
// Pseudo-operations (do not appear in LLVM IR but necessary for the dialect to
// work correctly).
def LLVM_UndefPseudoOp : LLVM_OneResultOp<"pseudo.undef", [NoSideEffect]>;
def LLVM_ConstantOp : LLVM_OneResultOp<"pseudo.constant", [NoSideEffect]>,
Arguments<(ins)>;
#endif // LLVMIR_OPS

View File

@ -0,0 +1,102 @@
//===- LLVMDialect.cpp - LLVM IR Ops and Dialect registration -------------===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
//
// This file defines the types and operation details for the LLVM IR dialect in
// MLIR, and the LLVM IR dialect. It also registers the dialect.
//
//===----------------------------------------------------------------------===//
#include "mlir/LLVMIR/LLVMDialect.h"
#include "mlir/IR/MLIRContext.h"
#include "llvm/AsmParser/Parser.h"
#include "llvm/IR/Type.h"
#include "llvm/Support/SourceMgr.h"
using namespace mlir;
using namespace mlir::LLVM;
namespace mlir {
namespace LLVM {
namespace detail {
class LLVMTypeStorage : public ::mlir::detail::TypeStorage {
public:
// LLVM types are pointer-unique.
using KeyTy = llvm::Type *;
KeyTy getKey() const { return underlyingType; }
static LLVMTypeStorage *construct(TypeStorageAllocator &allocator,
llvm::Type *t) {
auto *memory = allocator.allocate<LLVMTypeStorage>();
auto *storage = new (memory) LLVMTypeStorage;
storage->underlyingType = t;
return storage;
}
llvm::Type *underlyingType;
};
} // end namespace detail
} // end namespace LLVM
} // end namespace mlir
char LLVMType::typeID = '\0';
LLVMType LLVMType::get(MLIRContext *context, llvm::Type *llvmType) {
return Base::get(context, FIRST_LLVM_TYPE, llvmType);
}
static Type parseLLVMType(StringRef data, Location loc, MLIRContext *ctx) {
llvm::SMDiagnostic errorMessage;
auto *llvmDialect =
static_cast<LLVMDialect *>(ctx->getRegisteredDialect("llvm"));
assert(llvmDialect && "LLVM dialect not registered");
llvm::Type *type =
llvm::parseType(data, errorMessage, llvmDialect->getLLVMModule());
if (!type) {
ctx->emitError(loc, errorMessage.getMessage());
return {};
}
return LLVMType::get(ctx, type);
}
static void printLLVMType(Type ty, raw_ostream &os) {
auto type = ty.dyn_cast<LLVMType>();
assert(type && "printing wrong type");
assert(type.getUnderlyingType() && "no underlying LLVM type");
type.getUnderlyingType()->print(os);
}
llvm::Type *LLVMType::getUnderlyingType() const {
return static_cast<ImplType *>(type)->underlyingType;
}
/*---- LLVM IR Dialect and its registration ----------------------------- */
LLVMDialect::LLVMDialect(MLIRContext *context)
: Dialect("llvm", context), module("LLVMDialectModule", llvmContext) {
addTypes<LLVMType>();
#define GET_OP_LIST
addOperations<
#include "mlir/LLVMIR/llvm_ops.inc"
>();
typeParseHook = parseLLVMType;
typePrintHook = printLLVMType;
}
static DialectRegistration<LLVMDialect> llvmDialect;

View File

@ -0,0 +1,134 @@
// RUN: mlir-opt %s | FileCheck %s
// CHECK-LABEL: func @ops(%arg0: !llvm<"i32">, %arg1: !llvm<"float">)
func @ops(%arg0 : !llvm<"i32">, %arg1 : !llvm<"float">) {
// Integer artithmetics binary instructions.
//
// CHECK-NEXT: %0 = "llvm.add"(%arg0, %arg0) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
// CHECK-NEXT: %1 = "llvm.sub"(%arg0, %arg0) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
// CHECK-NEXT: %2 = "llvm.mul"(%arg0, %arg0) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
// CHECK-NEXT: %3 = "llvm.udiv"(%arg0, %arg0) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
// CHECK-NEXT: %4 = "llvm.sdiv"(%arg0, %arg0) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
// CHECK-NEXT: %5 = "llvm.urem"(%arg0, %arg0) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
// CHECK-NEXT: %6 = "llvm.srem"(%arg0, %arg0) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
// CHECK-NEXT: %7 = "llvm.icmp"(%arg0, %arg0) {predicate: 1} : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i1">
%0 = "llvm.add"(%arg0, %arg0) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
%1 = "llvm.sub"(%arg0, %arg0) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
%2 = "llvm.mul"(%arg0, %arg0) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
%3 = "llvm.udiv"(%arg0, %arg0) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
%4 = "llvm.sdiv"(%arg0, %arg0) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
%5 = "llvm.urem"(%arg0, %arg0) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
%6 = "llvm.srem"(%arg0, %arg0) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
%7 = "llvm.icmp"(%arg0, %arg0) {predicate: 1} : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i1">
// Floating point binary instructions.
//
// CHECK-NEXT: %8 = "llvm.fadd"(%arg1, %arg1) : (!llvm<"float">, !llvm<"float">) -> !llvm<"float">
// CHECK-NEXT: %9 = "llvm.fsub"(%arg1, %arg1) : (!llvm<"float">, !llvm<"float">) -> !llvm<"float">
// CHECK-NEXT: %10 = "llvm.fmul"(%arg1, %arg1) : (!llvm<"float">, !llvm<"float">) -> !llvm<"float">
// CHECK-NEXT: %11 = "llvm.fdiv"(%arg1, %arg1) : (!llvm<"float">, !llvm<"float">) -> !llvm<"float">
// CHECK-NEXT: %12 = "llvm.frem"(%arg1, %arg1) : (!llvm<"float">, !llvm<"float">) -> !llvm<"float">
%8 = "llvm.fadd"(%arg1, %arg1) : (!llvm<"float">, !llvm<"float">) -> !llvm<"float">
%9 = "llvm.fsub"(%arg1, %arg1) : (!llvm<"float">, !llvm<"float">) -> !llvm<"float">
%10 = "llvm.fmul"(%arg1, %arg1) : (!llvm<"float">, !llvm<"float">) -> !llvm<"float">
%11 = "llvm.fdiv"(%arg1, %arg1) : (!llvm<"float">, !llvm<"float">) -> !llvm<"float">
%12 = "llvm.frem"(%arg1, %arg1) : (!llvm<"float">, !llvm<"float">) -> !llvm<"float">
// Memory-related instructions.
//
// CHECK-NEXT: %13 = "llvm.alloca"(%arg0) : (!llvm<"i32">) -> !llvm<"double*">
// CHECK-NEXT: %14 = "llvm.getelementptr"(%13, %arg0, %arg0) : (!llvm<"double*">, !llvm<"i32">, !llvm<"i32">) -> !llvm<"double*">
// CHECK-NEXT: %15 = "llvm.load"(%14) : (!llvm<"double*">) -> !llvm<"double">
// CHECK-NEXT: "llvm.store"(%15, %13) : (!llvm<"double">, !llvm<"double*">) -> ()
// CHECK-NEXT: %16 = "llvm.bitcast"(%13) : (!llvm<"double*">) -> !llvm<"i64*">
%13 = "llvm.alloca"(%arg0) : (!llvm<"i32">) -> !llvm<"double*">
%14 = "llvm.getelementptr"(%13, %arg0, %arg0) : (!llvm<"double*">, !llvm<"i32">, !llvm<"i32">) -> !llvm<"double*">
%15 = "llvm.load"(%14) : (!llvm<"double*">) -> !llvm<"double">
"llvm.store"(%15, %13) : (!llvm<"double">, !llvm<"double*">) -> ()
%16 = "llvm.bitcast"(%13) : (!llvm<"double*">) -> !llvm<"i64*">
// Function call-related instructions.
//
// CHECK-NEXT: %17 = "llvm.call"(%arg0) {callee: @foo : (!llvm<"i32">) -> !llvm<"{ i32, double, i32 }">} : (!llvm<"i32">) -> !llvm<"{ i32, double, i32 }">
// CHECK-NEXT: %18 = "llvm.extractvalue"(%17) {position: [0]} : (!llvm<"{ i32, double, i32 }">) -> !llvm<"i32">
// CHECK-NEXT: %19 = "llvm.insertvalue"(%17, %18) {position: [2]} : (!llvm<"{ i32, double, i32 }">, !llvm<"i32">) -> !llvm<"{ i32, double, i32 }">
%17 = "llvm.call"(%arg0) {callee: @foo : (!llvm<"i32">) -> !llvm<"{ i32, double, i32 }">}
: (!llvm<"i32">) -> !llvm<"{ i32, double, i32 }">
%18 = "llvm.extractvalue"(%17) {position: [0]} : (!llvm<"{ i32, double, i32 }">) -> !llvm<"i32">
%19 = "llvm.insertvalue"(%17, %18) {position: [2]} : (!llvm<"{ i32, double, i32 }">, !llvm<"i32">) -> !llvm<"{ i32, double, i32 }">
// Terminator instructions and their successors.
//
// CHECK: "llvm.br"()[^bb1] : () -> ()
"llvm.br"()[^bb1] : () -> ()
^bb1:
// CHECK: "llvm.cond_br"(%7)[^bb2, ^bb1] : (!llvm<"i1">) -> ()
"llvm.cond_br"(%7)[^bb2,^bb1] : (!llvm<"i1">) -> ()
^bb2:
// CHECK: %20 = "llvm.pseudo.undef"() : () -> !llvm<"{ i32, double, i32 }">
// CHECK-NEXT: %21 = "llvm.pseudo.constant"() {value: 42} : () -> !llvm<"i47">
// CHECK-NEXT: "llvm.return"() : () -> ()
%20 = "llvm.pseudo.undef"() : () -> !llvm<"{ i32, double, i32 }">
%21 = "llvm.pseudo.constant"() {value: 42} : () -> !llvm<"i47">
"llvm.return"() : () -> ()
}
// An larger self-contained function.
// CHECK-LABEL:func @foo(%arg0: !llvm<"i32">) -> !llvm<"{ i32, double, i32 }"> {
func @foo(%arg0: !llvm<"i32">) -> !llvm<"{ i32, double, i32 }"> {
// CHECK-NEXT: %0 = "llvm.pseudo.constant"() {value: 3} : () -> !llvm<"i32">
// CHECK-NEXT: %1 = "llvm.pseudo.constant"() {value: 3} : () -> !llvm<"i32">
// CHECK-NEXT: %2 = "llvm.pseudo.constant"() {value: 4.200000e+01} : () -> !llvm<"double">
// CHECK-NEXT: %3 = "llvm.pseudo.constant"() {value: 4.200000e+01} : () -> !llvm<"double">
// CHECK-NEXT: %4 = "llvm.add"(%0, %1) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
// CHECK-NEXT: %5 = "llvm.mul"(%4, %1) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
// CHECK-NEXT: %6 = "llvm.fadd"(%2, %3) : (!llvm<"double">, !llvm<"double">) -> !llvm<"double">
// CHECK-NEXT: %7 = "llvm.fsub"(%3, %6) : (!llvm<"double">, !llvm<"double">) -> !llvm<"double">
// CHECK-NEXT: %8 = "llvm.pseudo.constant"() {value: 1} : () -> !llvm<"i1">
// CHECK-NEXT: "llvm.cond_br"(%8)[^bb1(%4 : !llvm<"i32">), ^bb2(%4 : !llvm<"i32">)] : (!llvm<"i1">) -> ()
%0 = "llvm.pseudo.constant"() {value: 3} : () -> !llvm<"i32">
%1 = "llvm.pseudo.constant"() {value: 3} : () -> !llvm<"i32">
%2 = "llvm.pseudo.constant"() {value: 4.200000e+01} : () -> !llvm<"double">
%3 = "llvm.pseudo.constant"() {value: 4.200000e+01} : () -> !llvm<"double">
%4 = "llvm.add"(%0, %1) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
%5 = "llvm.mul"(%4, %1) : (!llvm<"i32">, !llvm<"i32">) -> !llvm<"i32">
%6 = "llvm.fadd"(%2, %3) : (!llvm<"double">, !llvm<"double">) -> !llvm<"double">
%7 = "llvm.fsub"(%3, %6) : (!llvm<"double">, !llvm<"double">) -> !llvm<"double">
%8 = "llvm.pseudo.constant"() {value: 1} : () -> !llvm<"i1">
"llvm.cond_br"(%8)[^bb1(%4 : !llvm<"i32">), ^bb2(%4 : !llvm<"i32">)] : (!llvm<"i1">) -> ()
// CHECK-NEXT:^bb1(%9: !llvm<"i32">): // pred: ^bb0
// CHECK-NEXT: %10 = "llvm.call"(%9) {callee: @foo : (!llvm<"i32">) -> !llvm<"{ i32, double, i32 }">} : (!llvm<"i32">) -> !llvm<"{ i32, double, i32 }">
// CHECK-NEXT: %11 = "llvm.extractvalue"(%10) {position: [0]} : (!llvm<"{ i32, double, i32 }">) -> !llvm<"i32">
// CHECK-NEXT: %12 = "llvm.extractvalue"(%10) {position: [1]} : (!llvm<"{ i32, double, i32 }">) -> !llvm<"double">
// CHECK-NEXT: %13 = "llvm.extractvalue"(%10) {position: [2]} : (!llvm<"{ i32, double, i32 }">) -> !llvm<"i32">
// CHECK-NEXT: %14 = "llvm.pseudo.undef"() : () -> !llvm<"{ i32, double, i32 }">
// CHECK-NEXT: %15 = "llvm.insertvalue"(%14, %5) {position: [0]} : (!llvm<"{ i32, double, i32 }">, !llvm<"i32">) -> !llvm<"{ i32, double, i32 }">
// CHECK-NEXT: %16 = "llvm.insertvalue"(%15, %7) {position: [1]} : (!llvm<"{ i32, double, i32 }">, !llvm<"double">) -> !llvm<"{ i32, double, i32 }">
// CHECK-NEXT: %17 = "llvm.insertvalue"(%16, %11) {position: [2]} : (!llvm<"{ i32, double, i32 }">, !llvm<"i32">) -> !llvm<"{ i32, double, i32 }">
// CHECK-NEXT: "llvm.return"(%17) : (!llvm<"{ i32, double, i32 }">) -> ()
^bb1(%9: !llvm<"i32">): // pred: ^bb0
%10 = "llvm.call"(%9) {callee: @foo : (!llvm<"i32">) -> !llvm<"{ i32, double, i32 }">} : (!llvm<"i32">) -> !llvm<"{ i32, double, i32 }">
%11 = "llvm.extractvalue"(%10) {position: [0]} : (!llvm<"{ i32, double, i32 }">) -> !llvm<"i32">
%12 = "llvm.extractvalue"(%10) {position: [1]} : (!llvm<"{ i32, double, i32 }">) -> !llvm<"double">
%13 = "llvm.extractvalue"(%10) {position: [2]} : (!llvm<"{ i32, double, i32 }">) -> !llvm<"i32">
%14 = "llvm.pseudo.undef"() : () -> !llvm<"{ i32, double, i32 }">
%15 = "llvm.insertvalue"(%14, %5) {position: [0]} : (!llvm<"{ i32, double, i32 }">, !llvm<"i32">) -> !llvm<"{ i32, double, i32 }">
%16 = "llvm.insertvalue"(%15, %7) {position: [1]} : (!llvm<"{ i32, double, i32 }">, !llvm<"double">) -> !llvm<"{ i32, double, i32 }">
%17 = "llvm.insertvalue"(%16, %11) {position: [2]} : (!llvm<"{ i32, double, i32 }">, !llvm<"i32">) -> !llvm<"{ i32, double, i32 }">
"llvm.return"(%17) : (!llvm<"{ i32, double, i32 }">) -> ()
// CHECK-NEXT:^bb2(%18: !llvm<"i32">): // pred: ^bb0
// CHECK-NEXT: %19 = "llvm.pseudo.undef"() : () -> !llvm<"{ i32, double, i32 }">
// CHECK-NEXT: %20 = "llvm.insertvalue"(%19, %18) {position: [0]} : (!llvm<"{ i32, double, i32 }">, !llvm<"i32">) -> !llvm<"{ i32, double, i32 }">
// CHECK-NEXT: %21 = "llvm.insertvalue"(%20, %7) {position: [1]} : (!llvm<"{ i32, double, i32 }">, !llvm<"double">) -> !llvm<"{ i32, double, i32 }">
// CHECK-NEXT: %22 = "llvm.insertvalue"(%21, %5) {position: [2]} : (!llvm<"{ i32, double, i32 }">, !llvm<"i32">) -> !llvm<"{ i32, double, i32 }">
// CHECK-NEXT: "llvm.return"(%22) : (!llvm<"{ i32, double, i32 }">) -> ()
^bb2(%18: !llvm<"i32">): // pred: ^bb0
%19 = "llvm.pseudo.undef"() : () -> !llvm<"{ i32, double, i32 }">
%20 = "llvm.insertvalue"(%19, %18) {position: [0]} : (!llvm<"{ i32, double, i32 }">, !llvm<"i32">) -> !llvm<"{ i32, double, i32 }">
%21 = "llvm.insertvalue"(%20, %7) {position: [1]} : (!llvm<"{ i32, double, i32 }">, !llvm<"double">) -> !llvm<"{ i32, double, i32 }">
%22 = "llvm.insertvalue"(%21, %5) {position: [2]} : (!llvm<"{ i32, double, i32 }">, !llvm<"i32">) -> !llvm<"{ i32, double, i32 }">
"llvm.return"(%22) : (!llvm<"{ i32, double, i32 }">) -> ()
}