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