Add support for multiple results to the printer/parser, add support
for forward references to the parser, add initial support for SSA
use-list iteration and RAUW.
PiperOrigin-RevId: 205484031
diff --git a/include/mlir/IR/CFGValue.h b/include/mlir/IR/CFGValue.h
index 95956f3..d18d395 100644
--- a/include/mlir/IR/CFGValue.h
+++ b/include/mlir/IR/CFGValue.h
@@ -57,18 +57,24 @@
/// Instruction results are CFG Values.
class InstResult : public CFGValue {
public:
- InstResult(Type *type, Instruction *owner)
+ InstResult(Type *type, OperationInst *owner)
: CFGValue(CFGValueKind::InstResult, type), owner(owner) {}
static bool classof(const SSAValue *value) {
return value->getKind() == SSAValueKind::InstResult;
}
+ OperationInst *getOwner() { return owner; }
+ const OperationInst *getOwner() const { return owner; }
+
+ /// Return the number of this result.
+ unsigned getResultNumber() const;
+
private:
/// The owner of this operand.
/// TODO: can encode this more efficiently to avoid the space hit of this
/// through bitpacking shenanigans.
- Instruction *const owner;
+ OperationInst *const owner;
};
} // namespace mlir
diff --git a/include/mlir/IR/Instructions.h b/include/mlir/IR/Instructions.h
index 16073dd..52047d3 100644
--- a/include/mlir/IR/Instructions.h
+++ b/include/mlir/IR/Instructions.h
@@ -56,7 +56,7 @@
CFGFunction *getFunction() const;
/// Destroy this instruction and its subclass data.
- static void destroy(Instruction *inst);
+ void destroy();
void print(raw_ostream &os) const;
void dump() const;
@@ -95,7 +95,6 @@
ArrayRef<Type *> resultTypes,
ArrayRef<NamedAttribute> attributes,
MLIRContext *context);
- ~OperationInst();
unsigned getNumOperands() const { return numOperands; }
@@ -139,6 +138,8 @@
/// Unlink this instruction from its BasicBlock and delete it.
void eraseFromBlock();
+ void destroy();
+
/// Methods for support type inquiry through isa, cast, and dyn_cast.
static bool classof(const Instruction *inst) {
return inst->getKind() == Kind::Operation;
@@ -152,6 +153,7 @@
OperationInst(Identifier name, unsigned numOperands, unsigned numResults,
ArrayRef<NamedAttribute> attributes, MLIRContext *context);
+ ~OperationInst();
// This stuff is used by the TrailingObjects template.
friend llvm::TrailingObjects<OperationInst, InstOperand, InstResult>;
@@ -237,9 +239,7 @@
using OperationInst = ::mlir::OperationInst;
using instr_iterator = simple_ilist<OperationInst>::iterator;
- static void deleteNode(OperationInst *inst) {
- OperationInst::destroy(inst);
- }
+ static void deleteNode(OperationInst *inst) { inst->destroy(); }
void addNodeToList(OperationInst *inst);
void removeNodeFromList(OperationInst *inst);
diff --git a/include/mlir/IR/SSAOperand.h b/include/mlir/IR/SSAOperand.h
index b696c80..c8ab7d5 100644
--- a/include/mlir/IR/SSAOperand.h
+++ b/include/mlir/IR/SSAOperand.h
@@ -56,6 +56,11 @@
~SSAOperand() { removeFromCurrent(); }
+ /// Return the next operand on the use-list of the value we are referring to.
+ /// This should generally only be used by the internal implementation details
+ /// of the SSA machinery.
+ SSAOperand *getNextOperandUsingThisValue() { return nextUse; }
+
private:
/// The value used as this operand. This can be null when in a
/// "dropAllUses" state.
@@ -115,6 +120,24 @@
/// The owner of this operand.
SSAOwnerTy *const owner;
};
+
+inline auto SSAValue::use_begin() const -> use_iterator {
+ return SSAValue::use_iterator(firstUse);
+}
+
+inline auto SSAValue::use_end() const -> use_iterator {
+ return SSAValue::use_iterator(nullptr);
+}
+
+inline auto SSAValue::getUses() const -> llvm::iterator_range<use_iterator> {
+ return {use_begin(), use_end()};
+}
+
+/// Returns true if this value has exactly one use.
+inline bool SSAValue::hasOneUse() const {
+ return firstUse && firstUse->getNextOperandUsingThisValue() == nullptr;
+}
+
} // namespace mlir
#endif
diff --git a/include/mlir/IR/SSAValue.h b/include/mlir/IR/SSAValue.h
index 9312f2e..09fd45b 100644
--- a/include/mlir/IR/SSAValue.h
+++ b/include/mlir/IR/SSAValue.h
@@ -28,7 +28,9 @@
#include "llvm/ADT/iterator_range.h"
namespace mlir {
+class OperationInst;
class SSAOperand;
+template <typename OperandType, typename OwnerType> class SSAValueUseIterator;
/// This enumerates all of the SSA value kinds in the MLIR system.
enum class SSAValueKind {
@@ -53,25 +55,33 @@
Type *getType() const { return typeAndKind.getPointer(); }
- /// Replace every use of this value with the corresponding value 'newVal'.
- ///
- void replaceAllUsesWith(SSAValue *newVal);
-
/// Returns true if this value has no uses.
bool use_empty() const { return firstUse == nullptr; }
- // TODO: using use_iterator = SSAValueUseIterator<SSAOperandTy>;
- // TODO: using use_range = llvm::iterator_range<use_iterator>;
-
- // TODO: inline use_iterator use_begin() const;
- // TODO: inline use_iterator use_end() const;
-
- /// Returns a range of all uses, which is useful for iterating over all uses.
- // TODO: inline use_range getUses() const;
-
/// Returns true if this value has exactly one use.
inline bool hasOneUse() const;
+ using use_iterator = SSAValueUseIterator<SSAOperand, void>;
+ using use_range = llvm::iterator_range<use_iterator>;
+
+ inline use_iterator use_begin() const;
+ inline use_iterator use_end() const;
+
+ /// Returns a range of all uses, which is useful for iterating over all uses.
+ inline use_range getUses() const;
+
+ /// Replace all uses of 'this' value with the new value, updating anything in
+ /// the IR that uses 'this' to use the other value instead. When this returns
+ /// there are zero uses of 'this'.
+ void replaceAllUsesWith(SSAValue *newValue);
+
+ /// If this value is the result of an OperationInst, return the instruction
+ /// that defines it.
+ OperationInst *getDefiningInst();
+ const OperationInst *getDefiningInst() const {
+ return const_cast<SSAValue *>(this)->getDefiningInst();
+ }
+
protected:
SSAValue(SSAValueKind kind, Type *type) : typeAndKind(type, kind) {}
@@ -109,6 +119,46 @@
// FIXME: Implement SSAValueUseIterator here.
+/// An iterator over all uses of a ValueBase.
+template <typename OperandType, typename OwnerType>
+class SSAValueUseIterator
+ : public std::iterator<std::forward_iterator_tag, SSAOperand> {
+public:
+ SSAValueUseIterator() = default;
+ explicit SSAValueUseIterator(SSAOperand *current) : current(current) {}
+ OperandType *operator->() const { return current; }
+ OperandType &operator*() const { return current; }
+
+ template<typename SFINAE_Owner = OwnerType>
+ typename std::enable_if<!std::is_void<OwnerType>::value, SFINAE_Owner>::type
+ getUser() const {
+ return current->getOwner();
+ }
+
+ SSAValueUseIterator &operator++() {
+ assert(current && "incrementing past end()!");
+ current = (OperandType *)current->getNextOperandUsingThisValue();
+ return *this;
+ }
+
+ SSAValueUseIterator operator++(int unused) {
+ SSAValueUseIterator copy = *this;
+ ++*this;
+ return copy;
+ }
+
+ friend bool operator==(SSAValueUseIterator lhs, SSAValueUseIterator rhs) {
+ return lhs.current == rhs.current;
+ }
+
+ friend bool operator!=(SSAValueUseIterator lhs, SSAValueUseIterator rhs) {
+ return !(lhs == rhs);
+ }
+
+private:
+ OperandType *current;
+};
+
} // namespace mlir
#endif
diff --git a/lib/IR/AsmPrinter.cpp b/lib/IR/AsmPrinter.cpp
index b56c775..ff6ce3e 100644
--- a/lib/IR/AsmPrinter.cpp
+++ b/lib/IR/AsmPrinter.cpp
@@ -519,14 +519,30 @@
valueIDs[value] = nextValueID++;
}
- void printValueID(const SSAValue *value) const {
- // TODO: If this is the result of an operation with multiple results, look
- // up the first result, and print the #32 syntax.
- auto it = valueIDs.find(value);
- if (it != valueIDs.end())
- os << '%' << it->getSecond();
- else
+ void printValueID(const SSAValue *value,
+ bool dontPrintResultNo = false) const {
+ int resultNo = -1;
+ auto lookupValue = value;
+
+ // If this is a reference to the result of a multi-result instruction, print
+ // out the # identifier and make sure to map our lookup to the first result
+ // of the instruction.
+ if (auto *result = dyn_cast<InstResult>(value)) {
+ if (result->getOwner()->getNumResults() != 1) {
+ resultNo = result->getResultNumber();
+ lookupValue = result->getOwner()->getResult(0);
+ }
+ }
+
+ auto it = valueIDs.find(lookupValue);
+ if (it == valueIDs.end()) {
os << "<<INVALID SSA VALUE>>";
+ return;
+ }
+
+ os << '%' << it->getSecond();
+ if (resultNo != -1 && !dontPrintResultNo)
+ os << '#' << resultNo;
}
private:
@@ -543,7 +559,7 @@
// Operation this check can go away.
if (auto *inst = dyn_cast<OperationInst>(op)) {
if (inst->getNumResults()) {
- printValueID(inst->getResult(0));
+ printValueID(inst->getResult(0), /*dontPrintResultNo*/ true);
os << " = ";
}
}
diff --git a/lib/IR/Instructions.cpp b/lib/IR/Instructions.cpp
index 57cedfe..b925cef 100644
--- a/lib/IR/Instructions.cpp
+++ b/lib/IR/Instructions.cpp
@@ -19,6 +19,23 @@
#include "mlir/IR/BasicBlock.h"
using namespace mlir;
+/// Replace all uses of 'this' value with the new value, updating anything in
+/// the IR that uses 'this' to use the other value instead. When this returns
+/// there are zero uses of 'this'.
+void SSAValue::replaceAllUsesWith(SSAValue *newValue) {
+ assert(this != newValue && "cannot RAUW a value with itself");
+ while (!use_empty()) {
+ use_begin()->set(newValue);
+ }
+}
+
+/// Return the result number of this result.
+unsigned InstResult::getResultNumber() const {
+ // Results are always stored consecutively, so use pointer subtraction to
+ // figure out what number this is.
+ return this - &getOwner()->getInstResults()[0];
+}
+
//===----------------------------------------------------------------------===//
// Instruction
//===----------------------------------------------------------------------===//
@@ -30,20 +47,25 @@
}
/// Destroy this instruction or one of its subclasses.
-void Instruction::destroy(Instruction *inst) {
- switch (inst->getKind()) {
+void Instruction::destroy() {
+ switch (getKind()) {
case Kind::Operation:
- delete cast<OperationInst>(inst);
+ cast<OperationInst>(this)->destroy();
break;
case Kind::Branch:
- delete cast<BranchInst>(inst);
+ delete cast<BranchInst>(this);
break;
case Kind::Return:
- delete cast<ReturnInst>(inst);
+ delete cast<ReturnInst>(this);
break;
}
}
+void OperationInst::destroy() {
+ this->~OperationInst();
+ free(this);
+}
+
CFGFunction *Instruction::getFunction() const {
return getBlock()->getFunction();
}
@@ -60,7 +82,7 @@
MLIRContext *context) {
auto byteSize = totalSizeToAlloc<InstOperand, InstResult>(operands.size(),
resultTypes.size());
- void *rawMem = ::operator new(byteSize);
+ void *rawMem = malloc(byteSize);
// Initialize the OperationInst part of the instruction.
auto inst = ::new (rawMem) OperationInst(
@@ -141,6 +163,14 @@
getBlock()->getOperations().erase(this);
}
+/// If this value is the result of an OperationInst, return the instruction
+/// that defines it.
+OperationInst *SSAValue::getDefiningInst() {
+ if (auto *result = dyn_cast<InstResult>(this))
+ return result->getOwner();
+ return nullptr;
+}
+
//===----------------------------------------------------------------------===//
// Terminators
//===----------------------------------------------------------------------===//
@@ -149,7 +179,7 @@
void TerminatorInst::eraseFromBlock() {
assert(getBlock() && "Instruction has no parent");
getBlock()->setTerminator(nullptr);
- TerminatorInst::destroy(this);
+ destroy();
}
diff --git a/lib/Parser/Parser.cpp b/lib/Parser/Parser.cpp
index da936db..1fd3432 100644
--- a/lib/Parser/Parser.cpp
+++ b/lib/Parser/Parser.cpp
@@ -30,6 +30,7 @@
#include "mlir/IR/OperationSet.h"
#include "mlir/IR/Statements.h"
#include "mlir/IR/Types.h"
+#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/SourceMgr.h"
using namespace mlir;
using llvm::SourceMgr;
@@ -1179,10 +1180,19 @@
public:
FunctionParser(ParserState &state) : Parser(state) {}
- /// This represents a use of an SSA value in the program. This tracks
- /// location information in case this ends up being a use of an undefined
- /// value.
- typedef std::pair<StringRef, SMLoc> SSAUseInfo;
+ /// After the function is finished parsing, this function checks to see if
+ /// there are any remaining issues.
+ ParseResult finalizeFunction();
+
+ /// This represents a use of an SSA value in the program. The first two
+ /// entries in the tuple are the name and result number of a reference. The
+ /// third is the location of the reference, which is used in case this ends up
+ /// being a use of an undefined value.
+ struct SSAUseInfo {
+ StringRef name; // Value name, e.g. %42 or %abc
+ unsigned number; // Number, specified with #12
+ SMLoc loc; // Location of first definition or use.
+ };
/// Given a reference to an SSA value and its type, return a reference. This
/// returns null on failure.
@@ -1205,47 +1215,122 @@
private:
/// This keeps track of all of the SSA values we are tracking, indexed by
- /// their name (either an identifier or a number).
- llvm::StringMap<std::pair<SSAValue *, SMLoc>> values;
+ /// their name. This has one entry per result number.
+ llvm::StringMap<SmallVector<std::pair<SSAValue *, SMLoc>, 1>> values;
+
+ /// These are all of the placeholders we've made along with the location of
+ /// their first reference, to allow checking for use of undefined values.
+ DenseMap<SSAValue *, SMLoc> forwardReferencePlaceholders;
+
+ SSAValue *createForwardReferencePlaceholder(SMLoc loc, Type *type);
+
+ /// Return true if this is a forward reference.
+ bool isForwardReferencePlaceholder(SSAValue *value) {
+ return forwardReferencePlaceholders.count(value);
+ }
};
} // end anonymous namespace
+/// Create and remember a new placeholder for a forward reference.
+SSAValue *FunctionParser::createForwardReferencePlaceholder(SMLoc loc,
+ Type *type) {
+ // Forward references are always created as instructions, even in ML
+ // functions, because we just need something with a def/use chain.
+ //
+ // We create these placeholders as having an empty name, which we know cannot
+ // be created through normal user input, allowing us to distinguish them.
+ auto name = Identifier::get("placeholder", getContext());
+ auto *inst = OperationInst::create(name, /*operands*/ {}, type, /*attrs*/ {},
+ getContext());
+ forwardReferencePlaceholders[inst->getResult(0)] = loc;
+ return inst->getResult(0);
+}
+
/// Given an unbound reference to an SSA value and its type, return a the value
/// it specifies. This returns null on failure.
SSAValue *FunctionParser::resolveSSAUse(SSAUseInfo useInfo, Type *type) {
+ auto &entries = values[useInfo.name];
+
// If we have already seen a value of this name, return it.
- auto it = values.find(useInfo.first);
- if (it != values.end()) {
+ if (useInfo.number < entries.size() && entries[useInfo.number].first) {
+ auto *result = entries[useInfo.number].first;
// Check that the type matches the other uses.
- auto result = it->second.first;
if (result->getType() == type)
return result;
- emitError(useInfo.second, "use of value '" + useInfo.first.str() +
- "' expects different type than prior uses");
- emitError(it->second.second, "prior use here");
+ emitError(useInfo.loc, "use of value '" + useInfo.name.str() +
+ "' expects different type than prior uses");
+ emitError(entries[useInfo.number].second, "prior use here");
return nullptr;
}
- // Otherwise we have a forward reference.
- // TODO: Handle forward references.
- emitError(useInfo.second, "undeclared or forward reference");
- return nullptr;
+ // Make sure we have enough slots for this.
+ if (entries.size() <= useInfo.number)
+ entries.resize(useInfo.number + 1);
+
+ // If the value has already been defined and this is an overly large result
+ // number, diagnose that.
+ if (entries[0].first && !isForwardReferencePlaceholder(entries[0].first))
+ return (emitError(useInfo.loc, "reference to invalid result number"),
+ nullptr);
+
+ // Otherwise, this is a forward reference. Create a placeholder and remember
+ // that we did so.
+ auto *result = createForwardReferencePlaceholder(useInfo.loc, type);
+ entries[useInfo.number].first = result;
+ entries[useInfo.number].second = useInfo.loc;
+ return result;
}
/// Register a definition of a value with the symbol table.
ParseResult FunctionParser::addDefinition(SSAUseInfo useInfo, SSAValue *value) {
+ auto &entries = values[useInfo.name];
- // If this is the first definition of this thing, then we are trivially done.
- auto insertInfo = values.insert({useInfo.first, {value, useInfo.second}});
- if (insertInfo.second)
- return ParseSuccess;
+ // Make sure there is a slot for this value.
+ if (entries.size() <= useInfo.number)
+ entries.resize(useInfo.number + 1);
- // If we already had a value, replace it with the new one and remove the
- // placeholder, only if it was a forward ref.
- // TODO: Handle forward references.
- emitError(useInfo.second, "redefinition of SSA value " + useInfo.first.str());
- return ParseFailure;
+ // If we already have an entry for this, check to see if it was a definition
+ // or a forward reference.
+ if (auto *existing = entries[useInfo.number].first) {
+ if (!isForwardReferencePlaceholder(existing)) {
+ emitError(useInfo.loc,
+ "redefinition of SSA value '" + useInfo.name + "'");
+ return emitError(entries[useInfo.number].second,
+ "previously defined here");
+ }
+
+ // If it was a forward reference, update everything that used it to use the
+ // actual definition instead, delete the forward ref, and remove it from our
+ // set of forward references we track.
+ existing->replaceAllUsesWith(value);
+ existing->getDefiningInst()->destroy();
+ forwardReferencePlaceholders.erase(existing);
+ }
+
+ entries[useInfo.number].first = value;
+ entries[useInfo.number].second = useInfo.loc;
+ return ParseSuccess;
+}
+
+/// After the function is finished parsing, this function checks to see if
+/// there are any remaining issues.
+ParseResult FunctionParser::finalizeFunction() {
+ // Check for any forward references that are left. If we find any, error out.
+ if (!forwardReferencePlaceholders.empty()) {
+ SmallVector<std::pair<const char *, SSAValue *>, 4> errors;
+ // Iteration over the map isn't determinstic, so sort by source location.
+ for (auto entry : forwardReferencePlaceholders)
+ errors.push_back({entry.second.getPointer(), entry.first});
+ llvm::array_pod_sort(errors.begin(), errors.end());
+
+ for (auto entry : errors)
+ emitError(SMLoc::getFromPointer(entry.first),
+ "use of undeclared SSA value name");
+ return ParseFailure;
+ }
+
+ return ParseSuccess;
}
/// Parse a SSA operand for an instruction or statement.
@@ -1254,10 +1339,21 @@
/// TODO: SSA Constants.
///
ParseResult FunctionParser::parseSSAUse(SSAUseInfo &result) {
- result.first = getTokenSpelling();
- result.second = getToken().getLoc();
+ result.name = getTokenSpelling();
+ result.number = 0;
+ result.loc = getToken().getLoc();
if (!consumeIf(Token::percent_identifier))
return emitError("expected SSA operand");
+
+ // If we have an affine map ID, it is a result number.
+ if (getToken().is(Token::hash_identifier)) {
+ if (auto value = getToken().getHashIdentifierNumber())
+ result.number = value.getValue();
+ else
+ return emitError("invalid SSA value result number");
+ consumeToken(Token::hash_identifier);
+ }
+
return ParseSuccess;
}
@@ -1403,7 +1499,8 @@
if (inst->getNumResults() == 0)
return emitError(loc, "cannot name an operation with no results");
- addDefinition({resultID, loc}, inst->getResult(0));
+ for (unsigned i = 0, e = inst->getNumResults(); i != e; ++i)
+ addDefinition({resultID, i, loc}, inst->getResult(i));
}
}
@@ -1474,7 +1571,8 @@
}
getModule()->functionList.push_back(function);
- return ParseSuccess;
+
+ return finalizeFunction();
}
/// Basic block declaration.
@@ -1612,7 +1710,7 @@
getModule()->functionList.push_back(function);
- return ParseSuccess;
+ return finalizeFunction();
}
/// For statement.
diff --git a/lib/Parser/Token.cpp b/lib/Parser/Token.cpp
index 2cf2d46..2d78168 100644
--- a/lib/Parser/Token.cpp
+++ b/lib/Parser/Token.cpp
@@ -35,7 +35,6 @@
SMRange Token::getLocRange() const {
return SMRange(getLoc(), getEndLoc());
}
-#include "llvm/Support/raw_ostream.h"
/// For an integer token, return its value as an unsigned. If it doesn't fit,
/// return None.
@@ -81,6 +80,16 @@
return getSpelling().drop_front().drop_back().str();
}
+/// Given a hash_identifier token like #123, try to parse the number out of
+/// the identifier, returning None if it is a named identifier like #x or
+/// if the integer doesn't fit.
+Optional<unsigned> Token::getHashIdentifierNumber() const {
+ assert(getKind() == hash_identifier);
+ unsigned result = 0;
+ if (spelling.drop_front().getAsInteger(10, result))
+ return None;
+ return result;
+}
/// Given a punctuation or keyword token kind, return the spelling of the
/// token as a string. Warning: This will abort on markers, identifiers and
diff --git a/lib/Parser/Token.h b/lib/Parser/Token.h
index f847bcf4..670bd94 100644
--- a/lib/Parser/Token.h
+++ b/lib/Parser/Token.h
@@ -83,6 +83,11 @@
/// For an inttype token, return its bitwidth.
Optional<unsigned> getIntTypeBitwidth() const;
+ /// Given a hash_identifier token like #123, try to parse the number out of
+ /// the identifier, returning None if it is a named identifier like #x or
+ /// if the integer doesn't fit.
+ Optional<unsigned> getHashIdentifierNumber() const;
+
/// Given a 'string' token, return its value, including removing the quote
/// characters and unescaping the contents of the string.
std::string getStringValue() const;
diff --git a/test/IR/parser-errors.mlir b/test/IR/parser-errors.mlir
index b2c7432..1d29c1b 100644
--- a/test/IR/parser-errors.mlir
+++ b/test/IR/parser-errors.mlir
@@ -176,11 +176,21 @@
cfgfunc @redef() {
bb42:
- %x = "dim"(){index: 0} : ()->i32
- %x = "dim"(){index: 0} : ()->i32 // expected-error {{redefinition of SSA value %x}}
+ %x = "dim"(){index: 0} : ()->i32 // expected-error {{previously defined here}}
+ %x = "dim"(){index: 0} : ()->i32 // expected-error {{redefinition of SSA value '%x'}}
return
}
+// -----
+
+cfgfunc @undef() {
+bb42:
+ %x = "xxx"(%y) : (i32)->i32 // expected-error {{use of undeclared SSA value}}
+ return
+}
+
+// -----
+
mlfunc @missing_rbrace() {
return %a
mlfunc @d {return} // expected-error {{expected ',' or '}'}}
diff --git a/test/IR/parser.mlir b/test/IR/parser.mlir
index 222fa12..d10b1b6 100644
--- a/test/IR/parser.mlir
+++ b/test/IR/parser.mlir
@@ -178,3 +178,21 @@
"addf"(%f, %f) : (f32,f32) -> f32
return
}
+
+// CHECK-LABEL: cfgfunc @ssa_values() {
+cfgfunc @ssa_values() {
+bb0: // CHECK: bb0:
+ // CHECK: %0 = "foo"() : () -> (i1, i17)
+ %0 = "foo"() : () -> (i1, i17)
+ br bb2
+
+bb1: // CHECK: bb1:
+ // CHECK: %1 = "baz"(%2#1, %2#0, %0#1) : (f32, i11, i17) -> i16
+ %1 = "baz"(%2#1, %2#0, %0#1) : (f32, i11, i17) -> i16
+ return
+
+bb2: // CHECK: bb2:
+ // CHECK: %2 = "bar"(%0#0, %0#1) : (i1, i17) -> (i11, f32)
+ %2 = "bar"(%0#0, %0#1) : (i1, i17) -> (i11, f32)
+ br bb1
+}
\ No newline at end of file