Add (de)serialization of EntryPointOp and ExecutionModeOp

Since the serialization of EntryPointOp contains the name of the
function as well, the function serialization emits the function name
using OpName instruction, which is used during deserialization to get
the correct function name.

PiperOrigin-RevId: 259158784
diff --git a/include/mlir/Dialect/SPIRV/SPIRVBase.td b/include/mlir/Dialect/SPIRV/SPIRVBase.td
index 66544f0..a5539bd 100644
--- a/include/mlir/Dialect/SPIRV/SPIRVBase.td
+++ b/include/mlir/Dialect/SPIRV/SPIRVBase.td
@@ -72,6 +72,7 @@
 
 // Begin opcode section. Generated from SPIR-V spec; DO NOT MODIFY!
 
+def SPV_OC_OpName              : I32EnumAttrCase<"OpName", 5>;
 def SPV_OC_OpMemoryModel       : I32EnumAttrCase<"OpMemoryModel", 14>;
 def SPV_OC_OpEntryPoint        : I32EnumAttrCase<"OpEntryPoint", 15>;
 def SPV_OC_OpExecutionMode     : I32EnumAttrCase<"OpExecutionMode", 16>;
@@ -92,11 +93,12 @@
 
 def SPV_OpcodeAttr :
     I32EnumAttr<"Opcode", "valid SPIR-V instructions", [
-      SPV_OC_OpMemoryModel, SPV_OC_OpEntryPoint, SPV_OC_OpExecutionMode,
-      SPV_OC_OpTypeVoid, SPV_OC_OpTypeFloat, SPV_OC_OpTypePointer,
-      SPV_OC_OpTypeFunction, SPV_OC_OpFunction, SPV_OC_OpFunctionParameter,
-      SPV_OC_OpFunctionEnd, SPV_OC_OpVariable, SPV_OC_OpLoad, SPV_OC_OpStore,
-      SPV_OC_OpDecorate, SPV_OC_OpCompositeExtract, SPV_OC_OpFMul, SPV_OC_OpReturn
+      SPV_OC_OpName, SPV_OC_OpMemoryModel, SPV_OC_OpEntryPoint,
+      SPV_OC_OpExecutionMode, SPV_OC_OpTypeVoid, SPV_OC_OpTypeFloat,
+      SPV_OC_OpTypePointer, SPV_OC_OpTypeFunction, SPV_OC_OpFunction,
+      SPV_OC_OpFunctionParameter, SPV_OC_OpFunctionEnd, SPV_OC_OpVariable,
+      SPV_OC_OpLoad, SPV_OC_OpStore, SPV_OC_OpDecorate, SPV_OC_OpCompositeExtract,
+      SPV_OC_OpFMul, SPV_OC_OpReturn
       ]> {
     let returnType = "::mlir::spirv::Opcode";
     let convertFromStorage = "static_cast<::mlir::spirv::Opcode>($_self.getInt())";
diff --git a/lib/Dialect/SPIRV/Serialization/Deserializer.cpp b/lib/Dialect/SPIRV/Serialization/Deserializer.cpp
index 41e5d9b..9da6d8a 100644
--- a/lib/Dialect/SPIRV/Serialization/Deserializer.cpp
+++ b/lib/Dialect/SPIRV/Serialization/Deserializer.cpp
@@ -32,6 +32,15 @@
 
 using namespace mlir;
 
+// Decodes a string literal in `words` starting at `wordIndex`. Update the
+// latter to point to the position in words after the string literal.
+static inline StringRef decodeStringLiteral(ArrayRef<uint32_t> words,
+                                            unsigned &wordIndex) {
+  StringRef str(reinterpret_cast<const char *>(words.data() + wordIndex));
+  wordIndex += str.size() / 4 + 1;
+  return str;
+}
+
 namespace {
 /// A SPIR-V module serializer.
 ///
@@ -68,12 +77,18 @@
   /// Processes the SPIR-V OpMemoryModel with `operands` and updates `module`.
   LogicalResult processMemoryModel(ArrayRef<uint32_t> operands);
 
+  /// Process SPIR-V OpName with `operands`
+  LogicalResult processName(ArrayRef<uint32_t> operands);
+
   /// Processes the SPIR-V function at the current `offset` into `binary`.
   /// The operands to the OpFunction instruction is passed in as ``operands`.
   /// This method processes each instruction inside the function and dispatches
   /// them to their handler method accordingly.
   LogicalResult processFunction(ArrayRef<uint32_t> operands);
 
+  /// Get the FuncOp associated with a result <id> of OpFunction.
+  FuncOp getFunction(uint32_t id) { return funcMap.lookup(id); }
+
   //===--------------------------------------------------------------------===//
   // Type
   //===--------------------------------------------------------------------===//
@@ -105,8 +120,13 @@
   /// Processes a SPIR-V instruction with the given `opcode` and `operands`.
   /// This method is the main entrance for handling SPIR-V instruction; it
   /// checks the instruction opcode and dispatches to the corresponding handler.
+  /// Processing of Some instructions (like OpEntryPoint and OpExecutionMode)
+  /// might need to be defered, since they contain forward references to <id>s
+  /// in the deserialized binary, but module in SPIR-V dialect expects these to
+  /// be ssa-uses.
   LogicalResult processInstruction(spirv::Opcode opcode,
-                                   ArrayRef<uint32_t> operands);
+                                   ArrayRef<uint32_t> operands,
+                                   bool deferInstructions = true);
 
   /// Method to dispatch to the specialized deserialization function for an
   /// operation in SPIR-V dialect that is a mirror of an instruction in the
@@ -146,14 +166,27 @@
 
   OpBuilder opBuilder;
 
-  // result <id> to type mapping
+  // Result <id> to type mapping.
   DenseMap<uint32_t, Type> typeMap;
 
-  // result <id> to function mapping
-  DenseMap<uint32_t, Operation *> funcMap;
+  // Result <id> to function mapping.
+  DenseMap<uint32_t, FuncOp> funcMap;
 
-  // result <id> to value mapping
+  // Result <id> to value mapping.
   DenseMap<uint32_t, Value *> valueMap;
+
+  // Result <id> to name mapping.
+  DenseMap<uint32_t, StringRef> nameMap;
+
+  // List of instructions that are processed in a defered fashion (after an
+  // initial processing of the entire binary). Some operations like
+  // OpEntryPoint, and OpExecutionMode use forward references to function
+  // <id>s. In SPIR-V dialect the corresponding operations (spv.EntryPoint and
+  // spv.ExecutionMode) need these references resolved. So these instructions
+  // are deserialized and stored for processing once the entire binary is
+  // processed.
+  SmallVector<std::pair<spirv::Opcode, ArrayRef<uint32_t>>, 4>
+      deferedInstructions;
 };
 } // namespace
 
@@ -173,6 +206,12 @@
       return failure();
   }
 
+  for (auto &defered : deferedInstructions) {
+    if (failed(processInstruction(defered.first, defered.second, false))) {
+      return failure();
+    }
+  }
+
   return success();
 }
 
@@ -254,10 +293,14 @@
     return emitError(unknownLoc, "mismatch in function type ")
            << functionType << " and return type " << resultType << " specified";
   }
-  /// TODO : The function name must be obtained from OpName eventually
-  std::string fnName = "spirv_fn_" + std::to_string(operands[2]);
+
+  std::string fnName = nameMap.lookup(operands[1]).str();
+  if (fnName.empty()) {
+    fnName = "spirv_fn_" + std::to_string(operands[2]);
+  }
   auto funcOp = opBuilder.create<FuncOp>(unknownLoc, fnName, functionType,
                                          ArrayRef<NamedAttribute>());
+  funcMap[operands[1]] = funcOp;
   funcOp.addEntryBlock();
 
   // Parse the op argument instructions
@@ -319,6 +362,24 @@
   return success();
 }
 
+LogicalResult Deserializer::processName(ArrayRef<uint32_t> operands) {
+  if (operands.size() < 2) {
+    return emitError(unknownLoc, "OpName needs at least 2 operands");
+  }
+  if (!nameMap.lookup(operands[0]).empty()) {
+    return emitError(unknownLoc, "duplicate name found for result <id> ")
+           << operands[0];
+  }
+  unsigned wordIndex = 1;
+  StringRef name = decodeStringLiteral(operands, wordIndex);
+  if (wordIndex != operands.size()) {
+    return emitError(unknownLoc,
+                     "unexpected trailing words in OpName instruction");
+  }
+  nameMap[operands[0]] = name;
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // Type
 //===----------------------------------------------------------------------===//
@@ -437,12 +498,22 @@
 }
 
 LogicalResult Deserializer::processInstruction(spirv::Opcode opcode,
-                                               ArrayRef<uint32_t> operands) {
+                                               ArrayRef<uint32_t> operands,
+                                               bool deferInstructions) {
   // First dispatch all the instructions whose opcode does not correspond to
   // those that have a direct mirror in the SPIR-V dialect
   switch (opcode) {
   case spirv::Opcode::OpMemoryModel:
     return processMemoryModel(operands);
+  case spirv::Opcode::OpEntryPoint:
+  case spirv::Opcode::OpExecutionMode:
+    if (deferInstructions) {
+      deferedInstructions.emplace_back(opcode, operands);
+      return success();
+    }
+    break;
+  case spirv::Opcode::OpName:
+    return processName(operands);
   case spirv::Opcode::OpTypeFloat:
   case spirv::Opcode::OpTypeFunction:
   case spirv::Opcode::OpTypePointer:
@@ -450,12 +521,85 @@
     return processType(opcode, operands);
   case spirv::Opcode::OpFunction:
     return processFunction(operands);
-  default:;
+  default:
+    break;
   }
   return dispatchToAutogenDeserialization(opcode, operands);
 }
 
 namespace {
+template <>
+LogicalResult
+Deserializer::processOp<spirv::EntryPointOp>(ArrayRef<uint32_t> words) {
+  unsigned wordIndex = 0;
+  if (wordIndex >= words.size()) {
+    return emitError(unknownLoc,
+                     "missing Execution Model specification in OpEntryPoint");
+  }
+  auto exec_model = opBuilder.getI32IntegerAttr(words[wordIndex++]);
+  if (wordIndex >= words.size()) {
+    return emitError(unknownLoc, "missing <id> in OpEntryPoint");
+  }
+  // Get the function <id>
+  auto fnID = words[wordIndex++];
+  // Get the function name
+  auto fnName = decodeStringLiteral(words, wordIndex);
+  // Verify that the function <id> matches the fnName
+  auto parsedFunc = getFunction(fnID);
+  if (!parsedFunc) {
+    return emitError(unknownLoc, "no function matching <id> ") << fnID;
+  }
+  if (parsedFunc.getName() != fnName) {
+    return emitError(unknownLoc, "function name mismatch between OpEntryPoint "
+                                 "and OpFunction with <id> ")
+           << fnID << ": " << fnName << " vs. " << parsedFunc.getName();
+  }
+  SmallVector<Value *, 4> interface;
+  while (wordIndex < words.size()) {
+    auto arg = getValue(words[wordIndex]);
+    if (!arg) {
+      return emitError(unknownLoc, "undefined result <id> ")
+             << words[wordIndex] << " while decoding OpEntryPoint";
+    }
+    interface.push_back(arg);
+    wordIndex++;
+  }
+  opBuilder.create<spirv::EntryPointOp>(
+      unknownLoc, exec_model, opBuilder.getSymbolRefAttr(fnName), interface);
+  return success();
+}
+
+template <>
+LogicalResult
+Deserializer::processOp<spirv::ExecutionModeOp>(ArrayRef<uint32_t> words) {
+  unsigned wordIndex = 0;
+  if (wordIndex >= words.size()) {
+    return emitError(unknownLoc,
+                     "missing function result <id> in OpExecutionMode");
+  }
+  // Get the function <id> to get the name of the function
+  auto fnID = words[wordIndex++];
+  auto fn = getFunction(fnID);
+  if (!fn) {
+    return emitError(unknownLoc, "no function matching <id> ") << fnID;
+  }
+  // Get the Execution mode
+  if (wordIndex >= words.size()) {
+    return emitError(unknownLoc, "missing Execution Mode in OpExecutionMode");
+  }
+  auto execMode = opBuilder.getI32IntegerAttr(words[wordIndex++]);
+
+  // Get the values
+  SmallVector<Attribute, 4> attrListElems;
+  while (wordIndex < words.size()) {
+    attrListElems.push_back(opBuilder.getI32IntegerAttr(words[wordIndex++]));
+  }
+  auto values = opBuilder.getArrayAttr(attrListElems);
+  opBuilder.create<spirv::ExecutionModeOp>(
+      unknownLoc, opBuilder.getSymbolRefAttr(fn.getName()), execMode, values);
+  return success();
+}
+
 // Pull in auto-generated Deserializer::dispatchToAutogenDeserialization() and
 // various processOpImpl specializations.
 #define GET_DESERIALIZATION_FNS
diff --git a/lib/Dialect/SPIRV/Serialization/Serializer.cpp b/lib/Dialect/SPIRV/Serialization/Serializer.cpp
index 36acf13..18a2e10 100644
--- a/lib/Dialect/SPIRV/Serialization/Serializer.cpp
+++ b/lib/Dialect/SPIRV/Serialization/Serializer.cpp
@@ -46,6 +46,15 @@
   }
 }
 
+static inline void encodeStringLiteral(StringRef literal,
+                                       SmallVectorImpl<uint32_t> &buffer) {
+  // Encoding is the literal + null termination
+  auto encodingSize = literal.size() / 4 + 1;
+  auto bufferStartSize = buffer.size();
+  buffer.resize(bufferStartSize + encodingSize, 0);
+  std::memcpy(buffer.data() + bufferStartSize, literal.data(), literal.size());
+}
+
 namespace {
 
 /// A SPIR-V module serializer.
@@ -84,9 +93,11 @@
 
   LogicalResult processMemoryModel();
 
-  Optional<uint32_t> findFunctionID(Operation *op) const {
-    auto it = funcIDMap.find(op);
-    return it != funcIDMap.end() ? it->second : Optional<uint32_t>();
+  // It is illegal to use <id> 0 for SSA value in SPIR-V serialization. The
+  // method uses that to check if the function is defined in the serialized
+  // binary or not.
+  uint32_t findFunctionID(StringRef fnName) const {
+    return funcIDMap.lookup(fnName);
   }
 
   /// Processes a SPIR-V function op.
@@ -96,10 +107,10 @@
   // Types
   //===--------------------------------------------------------------------===//
 
-  Optional<uint32_t> findTypeID(Type type) const {
-    auto it = typeIDMap.find(type);
-    return it != typeIDMap.end() ? it->second : Optional<uint32_t>();
-  }
+  // It is illegal to use <id> 0 for SSA value in SPIR-V serialization. The
+  // method uses that to check if the type is defined in the serialized binary
+  // or not.
+  uint32_t findTypeID(Type type) const { return typeIDMap.lookup(type); }
 
   Type voidType() { return mlir::NoneType::get(module.getContext()); }
 
@@ -123,10 +134,9 @@
   // Operations
   //===--------------------------------------------------------------------===//
 
-  Optional<uint32_t> findValueID(Value *val) const {
-    auto it = valueIDMap.find(val);
-    return it != valueIDMap.end() ? it->second : Optional<uint32_t>();
-  }
+  // It is illegal to use <id> 0 for SSA value in SPIR-V serialization. The
+  // method uses that to check if `val` has a corresponding <id>
+  uint32_t findValueID(Value *val) const { return valueIDMap.lookup(val); }
 
   /// Main dispatch method for serializing an operation.
   LogicalResult processOperation(Operation *op);
@@ -166,17 +176,18 @@
   SmallVector<uint32_t, 0> entryPoints;
   SmallVector<uint32_t, 4> executionModes;
   // TODO(antiagainst): debug instructions
+  SmallVector<uint32_t, 0> names;
   SmallVector<uint32_t, 0> decorations;
   SmallVector<uint32_t, 0> typesGlobalValues;
   SmallVector<uint32_t, 0> functions;
 
-  // Map from type used in SPIR-V module to their IDs
+  // Map from type used in SPIR-V module to their <id>s
   DenseMap<Type, uint32_t> typeIDMap;
 
-  // Map from FuncOps to IDs
-  DenseMap<Operation *, uint32_t> funcIDMap;
+  // Map from FuncOps name to <id>s.
+  llvm::StringMap<uint32_t> funcIDMap;
 
-  // Map from Value to Ids
+  // Map from Value to Ids.
   DenseMap<Value *, uint32_t> valueIDMap;
 };
 } // namespace
@@ -216,6 +227,7 @@
   binary.append(memoryModel.begin(), memoryModel.end());
   binary.append(entryPoints.begin(), entryPoints.end());
   binary.append(executionModes.begin(), executionModes.end());
+  binary.append(names.begin(), names.end());
   binary.append(decorations.begin(), decorations.end());
   binary.append(typesGlobalValues.begin(), typesGlobalValues.end());
   binary.append(functions.begin(), functions.end());
@@ -250,7 +262,7 @@
   header.push_back(spirv::kMagicNumber);
   header.push_back((kMajorVersion << 16) | (kMinorVersion << 8));
   header.push_back(kGeneratorNumber);
-  header.push_back(nextID); // ID bound
+  header.push_back(nextID); // <id> bound
   header.push_back(0);      // Schema (reserved word)
   return success();
 }
@@ -265,10 +277,10 @@
 
 LogicalResult Serializer::processFuncOp(FuncOp op) {
   uint32_t fnTypeID = 0;
-  // Generate type of the function
+  // Generate type of the function.
   processType(op.getLoc(), op.getType(), fnTypeID);
 
-  // Add the function definition
+  // Add the function definition.
   SmallVector<uint32_t, 4> operands;
   uint32_t resTypeID = 0;
   auto resultTypes = op.getType().getResults();
@@ -283,14 +295,20 @@
   }
   operands.push_back(resTypeID);
   auto funcID = getNextID();
-  funcIDMap[op.getOperation()] = funcID;
+  funcIDMap[op.getName()] = funcID;
   operands.push_back(funcID);
-  // TODO : Support other function control options
+  // TODO : Support other function control options.
   operands.push_back(static_cast<uint32_t>(spirv::FunctionControl::None));
   operands.push_back(fnTypeID);
   buildInstruction(spirv::Opcode::OpFunction, operands, functions);
 
-  // Declare the parameters
+  // Add function name.
+  SmallVector<uint32_t, 4> nameOperands;
+  nameOperands.push_back(funcID);
+  encodeStringLiteral(op.getName(), nameOperands);
+  buildInstruction(spirv::Opcode::OpName, nameOperands, names);
+
+  // Declare the parameters.
   for (auto arg : op.getArguments()) {
     uint32_t argTypeID = 0;
     if (failed(processType(op.getLoc(), arg->getType(), argTypeID))) {
@@ -302,18 +320,20 @@
                      {argTypeID, argValueID}, functions);
   }
 
-  // Process the body
+  // Process the body.
   if (op.isExternal()) {
     return emitError(op.getLoc(), "external function is unhandled");
   }
 
-  for (auto &b : op)
-    for (auto &op : b)
+  for (auto &b : op) {
+    for (auto &op : b) {
       if (failed(processOperation(&op))) {
         return failure();
       }
+    }
+  }
 
-  // Insert Function End
+  // Insert Function End.
   buildInstruction(spirv::Opcode::OpFunctionEnd, {}, functions);
 
   return success();
@@ -325,9 +345,8 @@
 
 LogicalResult Serializer::processType(Location loc, Type type,
                                       uint32_t &typeID) {
-  auto id = findTypeID(type);
-  if (id) {
-    typeID = id.getValue();
+  typeID = findTypeID(type);
+  if (typeID) {
     return success();
   }
   typeID = getNextID();
@@ -366,7 +385,7 @@
     operands.push_back(pointeeTypeID);
     return success();
   }
-  /// TODO(ravishankarm) : Handle other types
+  // TODO(ravishankarm) : Handle other types.
   return emitError(loc, "unhandled type in serialization : ") << type;
 }
 
@@ -410,6 +429,66 @@
 }
 
 namespace {
+template <>
+LogicalResult
+Serializer::processOp<spirv::EntryPointOp>(spirv::EntryPointOp op) {
+  SmallVector<uint32_t, 4> operands;
+  // Add the ExectionModel.
+  operands.push_back(static_cast<uint32_t>(op.execution_model()));
+  // Add the function <id>.
+  auto funcID = findFunctionID(op.fn());
+  if (!funcID) {
+    return op.emitError("missing <id> for function ")
+           << op.fn()
+           << "; function needs to be defined before spv.EntryPoint is "
+              "serialized";
+  }
+  operands.push_back(funcID);
+  // Add the name of the function.
+  encodeStringLiteral(op.fn(), operands);
+
+  // Add the interface values.
+  for (auto val : op.interface()) {
+    auto id = findValueID(val);
+    if (!id) {
+      return op.emitError("referencing unintialized variable <id>. "
+                          "spv.EntryPoint is at the end of spv.module. All "
+                          "referenced variables should already be defined");
+    }
+    operands.push_back(id);
+  }
+  buildInstruction(spirv::Opcode::OpEntryPoint, operands, entryPoints);
+  return success();
+}
+
+template <>
+LogicalResult
+Serializer::processOp<spirv::ExecutionModeOp>(spirv::ExecutionModeOp op) {
+  SmallVector<uint32_t, 4> operands;
+  // Add the function <id>.
+  auto funcID = findFunctionID(op.fn());
+  if (!funcID) {
+    return op.emitError("missing <id> for function ")
+           << op.fn()
+           << "; function needs to be serialized before ExecutionModeOp is "
+              "serialized";
+  }
+  operands.push_back(funcID);
+  // Add the ExecutionMode.
+  operands.push_back(static_cast<uint32_t>(op.execution_mode()));
+
+  // Serialize values if any.
+  auto values = op.values();
+  if (values) {
+    for (auto &intVal : values.getValue()) {
+      operands.push_back(static_cast<uint32_t>(
+          intVal.cast<IntegerAttr>().getValue().getZExtValue()));
+    }
+  }
+  buildInstruction(spirv::Opcode::OpExecutionMode, operands, executionModes);
+  return success();
+}
+
 // Pull in auto-generated Serializer::dispatchToAutogenSerialization() and
 // various processOpImpl specializations.
 #define GET_SERIALIZATION_FNS
diff --git a/test/SPIRV/Serialization/entry.mlir b/test/SPIRV/Serialization/entry.mlir
new file mode 100644
index 0000000..c6093a9
--- /dev/null
+++ b/test/SPIRV/Serialization/entry.mlir
@@ -0,0 +1,14 @@
+// RUN: mlir-translate -serialize-spirv %s | mlir-translate -deserialize-spirv | FileCheck %s
+
+func @spirv_loadstore() -> () {
+  spv.module "Logical" "VulkanKHR" {
+    func @noop() -> () {
+      spv.Return
+    }
+    // CHECK:      spv.EntryPoint "GLCompute" @noop
+    // CHECK-NEXT: spv.ExecutionMode @noop "ContractionOff"
+    spv.EntryPoint "GLCompute" @noop
+    spv.ExecutionMode @noop "ContractionOff"
+  }
+  return
+}
\ No newline at end of file
diff --git a/test/SPIRV/Serialization/entry_interface.mlir b/test/SPIRV/Serialization/entry_interface.mlir
new file mode 100644
index 0000000..970e723
--- /dev/null
+++ b/test/SPIRV/Serialization/entry_interface.mlir
@@ -0,0 +1,18 @@
+// RUN: mlir-translate -serialize-spirv %s | mlir-translate -deserialize-spirv | FileCheck %s
+
+func @spirv_loadstore() -> () {
+  spv.module "Logical" "VulkanKHR" {
+    // CHECK:       [[VAR1:%.*]] = spv.Variable : !spv.ptr<f32, Input>
+    // CHECK-NEXT:  [[VAR2:%.*]] = spv.Variable : !spv.ptr<f32, Output>
+    // CHECK-NEXT:  func @noop({{%.*}}: !spv.ptr<f32, Input>, {{%.*}}: !spv.ptr<f32, Output>)
+    // CHECK:       spv.EntryPoint "GLCompute" @noop, [[VAR1]], [[VAR2]]
+    %2 = spv.Variable : !spv.ptr<f32, Input>
+    %3 = spv.Variable : !spv.ptr<f32, Output>
+    func @noop(%arg0 : !spv.ptr<f32, Input>, %arg1 : !spv.ptr<f32, Output>) -> () {
+      spv.Return
+    }
+    spv.EntryPoint "GLCompute" @noop, %2, %3 : !spv.ptr<f32, Input>, !spv.ptr<f32, Output>
+    spv.ExecutionMode @noop "ContractionOff"
+  }
+  return
+}
\ No newline at end of file
diff --git a/test/SPIRV/Serialization/execution_mode.mlir b/test/SPIRV/Serialization/execution_mode.mlir
new file mode 100644
index 0000000..173bb43
--- /dev/null
+++ b/test/SPIRV/Serialization/execution_mode.mlir
@@ -0,0 +1,13 @@
+// RUN: mlir-translate -serialize-spirv %s | mlir-translate -deserialize-spirv | FileCheck %s
+
+func @spirv_loadstore() -> () {
+  spv.module "Logical" "VulkanKHR" {
+    func @foo() -> () {
+      spv.Return
+    }
+    spv.EntryPoint "GLCompute" @foo
+    // CHECK: spv.ExecutionMode @foo "LocalSizeHint", 3, 4, 5
+    spv.ExecutionMode @foo "LocalSizeHint", 3, 4, 5
+  }
+  return
+}
\ No newline at end of file
diff --git a/test/SPIRV/Serialization/minimal-module.mlir b/test/SPIRV/Serialization/minimal-module.mlir
index ccdb48b..54b1762 100644
--- a/test/SPIRV/Serialization/minimal-module.mlir
+++ b/test/SPIRV/Serialization/minimal-module.mlir
@@ -2,7 +2,7 @@
 
 // CHECK-LABEL: func @spirv_module
 // CHECK:      spv.module "Logical" "VulkanKHR" {
-// CHECK-NEXT:   func @spirv_fn_0() {
+// CHECK-NEXT:   func @foo() {
 // CHECK-NEXT:     spv.Return
 // CHECK-NEXT:   }
 // CHECK-NEXT: } attributes {major_version = 1 : i32, minor_version = 0 : i32}
diff --git a/tools/mlir-tblgen/SPIRVUtilsGen.cpp b/tools/mlir-tblgen/SPIRVUtilsGen.cpp
index 458183f..b264fe4 100644
--- a/tools/mlir-tblgen/SPIRVUtilsGen.cpp
+++ b/tools/mlir-tblgen/SPIRVUtilsGen.cpp
@@ -134,7 +134,7 @@
       os << "        emitError(op.getLoc(), \"operand " << operandNum
          << " has a use before def\");\n";
       os << "      }\n";
-      os << "      operands.push_back(arg.getValue());\n";
+      os << "      operands.push_back(arg);\n";
       os << "    }\n";
       operandNum++;
     } else {