[Mlir-commits] [mlir] dddc56d - [mlir][SPIRV] Support Alignment and FuncParamAttr on parameters (#231069)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Wed Oct 14 04:23:30 PDT 2026


Author: Alexander Shaposhnikov
Date: 2026-10-14T11:23:25Z
New Revision: dddc56d58ae6105727f874d96e15f957b6d82f0a

URL: https://github.com/llvm/llvm-project/commit/dddc56d58ae6105727f874d96e15f957b6d82f0a
DIFF: https://github.com/llvm/llvm-project/commit/dddc56d58ae6105727f874d96e15f957b6d82f0a.diff

LOG: [mlir][SPIRV] Support Alignment and FuncParamAttr on parameters (#231069)

Clang-produced SPIR-V can decorate function parameters with Alignment
and one or more FuncParamAttr decorations. The deserializer currently
rejects these decorations, preventing such modules from being imported.

Represent Alignment as a signless i32 attribute and FuncParamAttr as an
array of typed FunctionParameterAttribute attributes. Preserve both when
deserializing and serializing function definitions and declarations,
including parameters that also carry an existing decoration.

Verify the attribute types, reject duplicate or mutually exclusive
parameter attributes, and enforce the pointer and Sret position
requirements. Add round-trip coverage for all core enumerants and an
alignment with its high bit set, verifier diagnostics, and malformed
binary tests.

Added: 
    mlir/test/Dialect/SPIRV/IR/function-parameter-attributes.mlir
    mlir/test/Target/SPIRV/function-parameter-attributes.mlir

Modified: 
    mlir/docs/Dialects/SPIR-V.md
    mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td
    mlir/include/mlir/Dialect/SPIRV/IR/TargetAndABI.h
    mlir/lib/Dialect/SPIRV/IR/SPIRVDialect.cpp
    mlir/lib/Dialect/SPIRV/IR/TargetAndABI.cpp
    mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
    mlir/lib/Target/SPIRV/Deserialization/Deserializer.h
    mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp
    mlir/lib/Target/SPIRV/Serialization/Serializer.cpp
    mlir/test/Target/SPIRV/global-variable.mlir
    mlir/test/Target/SPIRV/memory-ops.mlir
    mlir/unittests/Dialect/SPIRV/DeserializationTest.cpp

Removed: 
    


################################################################################
diff  --git a/mlir/docs/Dialects/SPIR-V.md b/mlir/docs/Dialects/SPIR-V.md
index dd68e6ec8b7b8b..f49abd8e348d66 100644
--- a/mlir/docs/Dialects/SPIR-V.md
+++ b/mlir/docs/Dialects/SPIR-V.md
@@ -242,6 +242,73 @@ decorated for memory layouting purpose (e.g., `ArrayStride`, `Offset`, etc.);
 scalar/vector types are required to be uniqued in SPIR-V. Therefore, we can just
 encode them directly in the dialect-specific type.
 
+### Function parameter and return decorations
+
+Decorations on `OpFunctionParameter` are represented by argument attributes on
+`spirv.func`. In addition to `spirv.decoration`, function parameters support:
+
+* `spirv.alignment`: a signless `i32` integer attribute holding the unsigned
+  32-bit `Alignment` literal. The parameter must have a SPIR-V pointer type.
+  Values with the high bit set print using a negative spelling without changing
+  the bit pattern.
+* `spirv.func_param_attr`: a non-empty array of
+  `#spirv.function_parameter_attribute<...>` enum attributes. Each element
+  represents one `FuncParamAttr` decoration. The supported core values are `Zext`,
+  `Sext`, `ByVal`, `Sret`, `NoAlias`, `NoCapture`, `NoWrite`, and `NoReadWrite`.
+
+For example, a pointer parameter can carry alignment, aliasing, and access
+information together:
+
+```mlir
+spirv.func @read(%p: !spirv.ptr<f32, CrossWorkgroup> {
+  spirv.alignment = 16 : i32,
+  spirv.decoration = #spirv.decoration<Restrict>,
+  spirv.func_param_attr = [#spirv.function_parameter_attribute<NoCapture>,
+                           #spirv.function_parameter_attribute<NoWrite>]
+}) "None" {
+  spirv.Return
+}
+```
+
+Serialization emits a separate `OpDecorate` for each function parameter
+attribute and preserves their order. Deserialization rejects unsupported
+parameter decorations, even when supported decorations are also present.
+
+`FuncParamAttr` on an `OpFunction` result id describes its return value. It is
+represented by a `spirv.func_param_attr` result attribute on `spirv.func`, stored
+in `res_attrs`, and serialized as a decoration of the function id. Only `Zext`
+and `Sext` are supported on results; both require an integer result. For example:
+
+```mlir
+spirv.func @extend(%v: i8) ->
+    (i8 {spirv.func_param_attr = [#spirv.function_parameter_attribute<Zext>]}) "None" {
+  spirv.ReturnValue %v : i8
+}
+```
+
+Parameter and result decorations are preserved on both function definitions and
+declarations. A `FuncParamAttr` decoration on a void function or a target other
+than `OpFunctionParameter` or `OpFunction` is diagnosed.
+
+The [SPIR-V specification, section 3.2.18](
+https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_a_id_function_parameter_attribute_a_function_parameter_attribute)
+explicitly states: "Only one of Zext and Sext can be used to decorate the same
+<id>" and "no attribute may be used multiple times". The verifier therefore
+rejects duplicate attributes and the combination of `Zext` with `Sext` on either
+a parameter or a result. The dialect requires integer parameters for `Zext` and
+`Sext`. The other supported attributes require pointer parameters, cannot appear
+on results, and `Sret` additionally requires the first parameter.
+
+`Alignment` is also preserved on module-scope and function-local variables, using
+an unprefixed `alignment` attribute on `spirv.GlobalVariable` and `spirv.Variable`:
+
+```mlir
+spirv.GlobalVariable @buffer {alignment = 16 : i32} : !spirv.ptr<f32, CrossWorkgroup>
+
+// Inside a function:
+%local = spirv.Variable {alignment = 16 : i32} : !spirv.ptr<f32, Function>
+```
+
 ## Types
 
 Theoretically we can define all SPIR-V types using MLIR extensible type system,

diff  --git a/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td b/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td
index 15864db78a76ce..7e799fc0191076 100644
--- a/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td
+++ b/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td
@@ -3364,6 +3364,54 @@ def SPIRV_FunctionControlAttr :
       SPIRV_FC_OptNoneINTEL
     ]>;
 
+def SPIRV_FPA_Zext        : I32EnumAttrCase<"Zext", 0> {
+  list<Availability> availability = [
+    Capability<[SPIRV_C_Kernel]>
+  ];
+}
+def SPIRV_FPA_Sext        : I32EnumAttrCase<"Sext", 1> {
+  list<Availability> availability = [
+    Capability<[SPIRV_C_Kernel]>
+  ];
+}
+def SPIRV_FPA_ByVal       : I32EnumAttrCase<"ByVal", 2> {
+  list<Availability> availability = [
+    Capability<[SPIRV_C_Kernel]>
+  ];
+}
+def SPIRV_FPA_Sret        : I32EnumAttrCase<"Sret", 3> {
+  list<Availability> availability = [
+    Capability<[SPIRV_C_Kernel]>
+  ];
+}
+def SPIRV_FPA_NoAlias     : I32EnumAttrCase<"NoAlias", 4> {
+  list<Availability> availability = [
+    Capability<[SPIRV_C_Kernel]>
+  ];
+}
+def SPIRV_FPA_NoCapture   : I32EnumAttrCase<"NoCapture", 5> {
+  list<Availability> availability = [
+    Capability<[SPIRV_C_Kernel]>
+  ];
+}
+def SPIRV_FPA_NoWrite     : I32EnumAttrCase<"NoWrite", 6> {
+  list<Availability> availability = [
+    Capability<[SPIRV_C_Kernel]>
+  ];
+}
+def SPIRV_FPA_NoReadWrite : I32EnumAttrCase<"NoReadWrite", 7> {
+  list<Availability> availability = [
+    Capability<[SPIRV_C_Kernel]>
+  ];
+}
+
+def SPIRV_FunctionParameterAttributeAttr :
+    SPIRV_I32EnumAttr<"FunctionParameterAttribute", "valid SPIR-V FunctionParameterAttribute", "function_parameter_attribute", [
+      SPIRV_FPA_Zext, SPIRV_FPA_Sext, SPIRV_FPA_ByVal, SPIRV_FPA_Sret,
+      SPIRV_FPA_NoAlias, SPIRV_FPA_NoCapture, SPIRV_FPA_NoWrite,
+      SPIRV_FPA_NoReadWrite
+    ]>;
+
 def SPIRV_GO_Reduce                     : I32EnumAttrCase<"Reduce", 0> {
   list<Availability> availability = [
     Capability<[SPIRV_C_GroupNonUniformArithmetic, SPIRV_C_GroupNonUniformBallot, SPIRV_C_Kernel]>

diff  --git a/mlir/include/mlir/Dialect/SPIRV/IR/TargetAndABI.h b/mlir/include/mlir/Dialect/SPIRV/IR/TargetAndABI.h
index 6e302542bde357..f1f38acc76372c 100644
--- a/mlir/include/mlir/Dialect/SPIRV/IR/TargetAndABI.h
+++ b/mlir/include/mlir/Dialect/SPIRV/IR/TargetAndABI.h
@@ -70,6 +70,13 @@ class TargetEnv {
   llvm::SmallSet<Capability, 8> givenCapabilities; /// Allowed capabilities
 };
 
+/// Returns the attribute name for specifying function parameter alignment.
+StringRef getFuncParamAlignmentAttrName();
+
+/// Returns the attribute name for specifying function parameter and result
+/// attributes.
+StringRef getFuncParamAttrName();
+
 /// Returns the attribute name for specifying argument ABI information.
 StringRef getInterfaceVarABIAttrName();
 

diff  --git a/mlir/lib/Dialect/SPIRV/IR/SPIRVDialect.cpp b/mlir/lib/Dialect/SPIRV/IR/SPIRVDialect.cpp
index 2ab675221bd118..a87cc552c48a68 100644
--- a/mlir/lib/Dialect/SPIRV/IR/SPIRVDialect.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/SPIRVDialect.cpp
@@ -29,6 +29,8 @@
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/TypeSwitch.h"
 
+#include <optional>
+
 using namespace mlir;
 using namespace mlir::spirv;
 
@@ -1097,6 +1099,61 @@ static LogicalResult verifyRegionAttribute(Location loc, Type valueType,
          << symbol << "' attribute on region argument";
 }
 
+/// Verifies FuncParamAttr decorations. A missing index denotes a function
+/// result.
+static LogicalResult verifyFunctionParameterAttributes(
+    Location loc, Type type, Attribute attr,
+    std::optional<unsigned> index = std::nullopt) {
+  auto attrs = dyn_cast<ArrayAttr>(attr);
+  if (!attrs || attrs.empty() ||
+      !llvm::all_of(attrs,
+                    llvm::IsaPred<spirv::FunctionParameterAttributeAttr>))
+    return emitError(loc, "'") << spirv::getFuncParamAttrName()
+                               << "' must be a non-empty array of "
+                                  "spirv::FunctionParameterAttributeAttr";
+
+  SmallVector<spirv::FunctionParameterAttribute, 8> seen;
+  bool hasZext = false, hasSext = false;
+  for (Attribute element : attrs) {
+    auto value =
+        cast<spirv::FunctionParameterAttributeAttr>(element).getValue();
+    if (llvm::is_contained(seen, value))
+      return emitError(loc, "duplicate function parameter attribute '")
+             << spirv::stringifyFunctionParameterAttribute(value) << "'";
+    seen.push_back(value);
+
+    switch (value) {
+    case spirv::FunctionParameterAttribute::Zext:
+    case spirv::FunctionParameterAttribute::Sext:
+      if (!isa<IntegerType>(type))
+        return emitError(loc, "function parameter attribute '")
+               << spirv::stringifyFunctionParameterAttribute(value)
+               << "' requires an integer " << (index ? "parameter" : "result");
+      hasZext |= value == spirv::FunctionParameterAttribute::Zext;
+      hasSext |= value == spirv::FunctionParameterAttribute::Sext;
+      break;
+    default:
+      if (!index)
+        return emitError(loc, "function parameter attribute '")
+               << spirv::stringifyFunctionParameterAttribute(value)
+               << "' is not valid on function results";
+      if (!isa<spirv::PointerType>(type))
+        return emitError(loc, "function parameter attribute '")
+               << spirv::stringifyFunctionParameterAttribute(value)
+               << "' requires a pointer parameter";
+      if (value == spirv::FunctionParameterAttribute::Sret && *index != 0)
+        return emitError(loc,
+                         "function parameter attribute 'Sret' requires the "
+                         "first parameter");
+      break;
+    }
+  }
+  if (hasZext && hasSext)
+    return emitError(loc, "function parameter attributes 'Zext' and 'Sext' are "
+                          "mutually exclusive");
+  return success();
+}
+
 LogicalResult SPIRVDialect::verifyRegionArgAttribute(Operation *op,
                                                      unsigned regionIndex,
                                                      unsigned argIndex,
@@ -1106,12 +1163,35 @@ LogicalResult SPIRVDialect::verifyRegionArgAttribute(Operation *op,
     return success();
   Type argType = funcOp.getArgumentTypes()[argIndex];
 
+  if (attribute.getName() == spirv::getFuncParamAlignmentAttrName()) {
+    auto alignment = dyn_cast<IntegerAttr>(attribute.getValue());
+    if (!alignment || !alignment.getType().isSignlessInteger(32))
+      return op->emitError("'")
+             << spirv::getFuncParamAlignmentAttrName()
+             << "' must be a 32-bit signless integer attribute";
+    if (!isa<spirv::PointerType>(argType))
+      return op->emitError("'") << spirv::getFuncParamAlignmentAttrName()
+                                << "' requires a pointer parameter";
+    return success();
+  }
+  if (attribute.getName() == spirv::getFuncParamAttrName())
+    return verifyFunctionParameterAttributes(op->getLoc(), argType,
+                                             attribute.getValue(), argIndex);
+
   return verifyRegionAttribute(op->getLoc(), argType, attribute);
 }
 
 LogicalResult SPIRVDialect::verifyRegionResultAttribute(
     Operation *op, unsigned /*regionIndex*/, unsigned resultIndex,
     NamedAttribute attribute) {
+  if (auto funcOp = dyn_cast<spirv::FuncOp>(op)) {
+    if (attribute.getName() == spirv::getFuncParamAttrName())
+      return verifyFunctionParameterAttributes(
+          op->getLoc(), funcOp.getResultTypes()[resultIndex],
+          attribute.getValue());
+    return op->emitError("found unsupported '")
+           << attribute.getName().strref() << "' attribute on function result";
+  }
   if (auto graphOp = dyn_cast<spirv::GraphARMOp>(op))
     return verifyRegionAttribute(
         op->getLoc(), graphOp.getResultTypes()[resultIndex], attribute);

diff  --git a/mlir/lib/Dialect/SPIRV/IR/TargetAndABI.cpp b/mlir/lib/Dialect/SPIRV/IR/TargetAndABI.cpp
index 4761a9a3e21aa9..5f1985c57bc7fb 100644
--- a/mlir/lib/Dialect/SPIRV/IR/TargetAndABI.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/TargetAndABI.cpp
@@ -92,6 +92,10 @@ MLIRContext *spirv::TargetEnv::getContext() const {
 // Utility functions
 //===----------------------------------------------------------------------===//
 
+StringRef spirv::getFuncParamAlignmentAttrName() { return "spirv.alignment"; }
+
+StringRef spirv::getFuncParamAttrName() { return "spirv.func_param_attr"; }
+
 StringRef spirv::getInterfaceVarABIAttrName() {
   return "spirv.interface_var_abi";
 }

diff  --git a/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp b/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
index 00b51dc68bdcda..1cdc5686adfeb2 100644
--- a/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
+++ b/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
@@ -16,6 +16,7 @@
 #include "mlir/Dialect/SPIRV/IR/SPIRVEnums.h"
 #include "mlir/Dialect/SPIRV/IR/SPIRVOps.h"
 #include "mlir/Dialect/SPIRV/IR/SPIRVTypes.h"
+#include "mlir/Dialect/SPIRV/IR/TargetAndABI.h"
 #include "mlir/IR/Builders.h"
 #include "mlir/IR/IRMapping.h"
 #include "mlir/IR/Location.h"
@@ -105,6 +106,11 @@ LogicalResult spirv::Deserializer::deserialize() {
   if (failed(resolveDeferredIdDecorations()))
     return failure();
 
+  if (!functionParameterAttrs.empty())
+    return emitError(unknownLoc, "FuncParamAttr decoration on result <id> ")
+           << functionParameterAttrs.begin()->first
+           << " is not supported: expected OpFunctionParameter or OpFunction";
+
   attachVCETriple();
 
   LLVM_DEBUG(logger.startLine()
@@ -290,6 +296,24 @@ LogicalResult spirv::Deserializer::processDecoration(ArrayRef<uint32_t> words) {
         symbol, FPRoundingModeAttr::get(opBuilder.getContext(),
                                         static_cast<FPRoundingMode>(words[2])));
     break;
+  case spirv::Decoration::FuncParamAttr: {
+    if (words.size() != 3)
+      return emitError(unknownLoc,
+                       "OpDecorate with FuncParamAttr needs a single "
+                       "function parameter attribute");
+    auto value = spirv::symbolizeFunctionParameterAttribute(words[2]);
+    if (!value)
+      return emitError(unknownLoc, "invalid FunctionParameterAttribute code: ")
+             << words[2];
+    SmallVector<Attribute> attrs;
+    if (auto previous = functionParameterAttrs.lookup(words[0]))
+      llvm::append_range(attrs, previous);
+    attrs.push_back(
+        spirv::FunctionParameterAttributeAttr::get(context, *value));
+    functionParameterAttrs[words[0]] = opBuilder.getArrayAttr(attrs);
+    break;
+  }
+  case spirv::Decoration::Alignment:
   case spirv::Decoration::DescriptorSet:
   case spirv::Decoration::Binding:
   case spirv::Decoration::Location:
@@ -486,13 +510,21 @@ LogicalResult spirv::Deserializer::processMemberName(ArrayRef<uint32_t> words) {
 
 LogicalResult spirv::Deserializer::setFunctionArgAttrs(
     uint32_t argID, SmallVectorImpl<Attribute> &argAttrs, size_t argIndex) {
-  if (!decorations.contains(argID)) {
-    argAttrs[argIndex] = DictionaryAttr::get(context, {});
-    return success();
-  }
-
   spirv::DecorationAttr foundDecorationAttr;
-  for (NamedAttribute decAttr : decorations[argID]) {
+  NamedAttrList attributes;
+  if (auto it = functionParameterAttrs.find(argID);
+      it != functionParameterAttrs.end()) {
+    attributes.set(spirv::getFuncParamAttrName(), it->second);
+    functionParameterAttrs.erase(it);
+  }
+  for (NamedAttribute decAttr : decorations.lookup(argID)) {
+    if (decAttr.getName() == getSymbolDecoration(stringifyDecoration(
+                                 spirv::Decoration::Alignment))) {
+      attributes.set(spirv::getFuncParamAlignmentAttrName(),
+                     decAttr.getValue());
+      continue;
+    }
+    bool handled = false;
     for (auto decoration :
          {spirv::Decoration::Aliased, spirv::Decoration::Restrict,
           spirv::Decoration::AliasedPointer,
@@ -509,8 +541,11 @@ LogicalResult spirv::Deserializer::setFunctionArgAttrs(
                << argID;
 
       foundDecorationAttr = spirv::DecorationAttr::get(context, decoration);
+      handled = true;
       break;
     }
+    if (handled)
+      continue;
 
     if (decAttr.getName() == getSymbolDecoration(stringifyDecoration(
                                  spirv::Decoration::RelaxedPrecision))) {
@@ -525,17 +560,17 @@ LogicalResult spirv::Deserializer::setFunctionArgAttrs(
 
       foundDecorationAttr = spirv::DecorationAttr::get(
           context, spirv::Decoration::RelaxedPrecision);
+      continue;
     }
-  }
 
-  if (!foundDecorationAttr)
-    return emitError(unknownLoc, "unimplemented decoration support for "
-                                 "function argument with result <id> ")
-           << argID;
+    return emitError(unknownLoc, "unhandled decoration '")
+           << decAttr.getName().getValue()
+           << "' on function argument with result <id> " << argID;
+  }
 
-  NamedAttribute attr(StringAttr::get(context, spirv::DecorationAttr::name),
-                      foundDecorationAttr);
-  argAttrs[argIndex] = DictionaryAttr::get(context, attr);
+  if (foundDecorationAttr)
+    attributes.set(spirv::DecorationAttr::name, foundDecorationAttr);
+  argAttrs[argIndex] = attributes.getDictionary(context);
   return success();
 }
 
@@ -582,6 +617,15 @@ spirv::Deserializer::processFunction(ArrayRef<uint32_t> operands) {
   std::string fnName = getFunctionSymbol(fnID);
   auto funcOp = spirv::FuncOp::create(opBuilder, unknownLoc, fnName,
                                       functionType, fnControl.value());
+  if (auto it = functionParameterAttrs.find(fnID);
+      it != functionParameterAttrs.end()) {
+    if (functionType.getNumResults() == 0)
+      return emitError(unknownLoc,
+                       "FuncParamAttr decoration requires a non-void function "
+                       "result");
+    funcOp.setResultAttr(0, spirv::getFuncParamAttrName(), it->second);
+    functionParameterAttrs.erase(it);
+  }
   // Processing other function attributes.
   if (decorations.count(fnID)) {
     for (auto attr : decorations[fnID].getAttrs()) {

diff  --git a/mlir/lib/Target/SPIRV/Deserialization/Deserializer.h b/mlir/lib/Target/SPIRV/Deserialization/Deserializer.h
index d2b7dbaad46552..543db3c99bddb0 100644
--- a/mlir/lib/Target/SPIRV/Deserialization/Deserializer.h
+++ b/mlir/lib/Target/SPIRV/Deserialization/Deserializer.h
@@ -706,6 +706,11 @@ class Deserializer {
   // Result <id> to decorations mapping.
   DenseMap<uint32_t, NamedAttrList> decorations;
 
+  // FuncParamAttr decorations awaiting an OpFunctionParameter or OpFunction.
+  // Keeping them separate prevents unsupported targets from acquiring
+  // discardable attributes that would be silently dropped during serialization.
+  DenseMap<uint32_t, ArrayAttr> functionParameterAttrs;
+
   // Decoration entries from OpDecorateId whose operand <id>s must be resolved
   // to MLIR symbols after all module ops have been deserialized.
   struct DeferredIdDecoration {

diff  --git a/mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp b/mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp
index 442064fcb6fe53..da311c386a3242 100644
--- a/mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp
+++ b/mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp
@@ -14,6 +14,7 @@
 
 #include "mlir/Dialect/SPIRV/IR/SPIRVAttributes.h"
 #include "mlir/Dialect/SPIRV/IR/SPIRVEnums.h"
+#include "mlir/Dialect/SPIRV/IR/TargetAndABI.h"
 #include "mlir/IR/RegionGraphTraits.h"
 #include "mlir/Target/SPIRV/SPIRVBinaryUtils.h"
 #include "mlir/Target/SPIRV/SPIRVExtInstSets.h"
@@ -268,6 +269,17 @@ LogicalResult Serializer::processFuncParameter(spirv::FuncOp op) {
     // Process decoration attributes of arguments.
     auto funcOp = cast<FunctionOpInterface>(*op);
     for (auto argAttr : funcOp.getArgAttrs(idx)) {
+      if (argAttr.getName() == spirv::getFuncParamAlignmentAttrName() ||
+          argAttr.getName() == spirv::getFuncParamAttrName()) {
+        auto decoration =
+            argAttr.getName() == spirv::getFuncParamAlignmentAttrName()
+                ? Decoration::Alignment
+                : Decoration::FuncParamAttr;
+        if (failed(processDecorationAttr(op.getLoc(), argValueID, decoration,
+                                         argAttr.getValue())))
+          return failure();
+        continue;
+      }
       if (argAttr.getName() != DecorationAttr::name)
         continue;
 
@@ -313,6 +325,13 @@ LogicalResult Serializer::processFuncOp(spirv::FuncOp op) {
   operands.push_back(fnTypeID);
   encodeInstructionInto(functionHeader, spirv::Opcode::OpFunction, operands);
 
+  if (!resultTypes.empty()) {
+    if (Attribute attr = op.getResultAttr(0, spirv::getFuncParamAttrName()))
+      if (failed(processDecorationAttr(op.getLoc(), funcID,
+                                       Decoration::FuncParamAttr, attr)))
+        return failure();
+  }
+
   // Add function name.
   if (failed(processName(funcID, op.getName()))) {
     return failure();

diff  --git a/mlir/lib/Target/SPIRV/Serialization/Serializer.cpp b/mlir/lib/Target/SPIRV/Serialization/Serializer.cpp
index d22d198ee8c074..44a96e90379636 100644
--- a/mlir/lib/Target/SPIRV/Serialization/Serializer.cpp
+++ b/mlir/lib/Target/SPIRV/Serialization/Serializer.cpp
@@ -371,6 +371,14 @@ LogicalResult Serializer::processDecorationAttr(Location loc, uint32_t resultID,
     }
     return emitError(loc, "expected FPRoundingModeAttr attribute for ")
            << stringifyDecoration(decoration);
+  case spirv::Decoration::FuncParamAttr:
+    return processDecorationList<FunctionParameterAttributeAttr>(
+        loc, decoration, attr, "FunctionParameterAttribute",
+        [&](FunctionParameterAttributeAttr attr) {
+          return emitDecoration(resultID, decoration,
+                                {static_cast<uint32_t>(attr.getValue())});
+        });
+  case spirv::Decoration::Alignment:
   case spirv::Decoration::Binding:
   case spirv::Decoration::DescriptorSet:
   case spirv::Decoration::Location:

diff  --git a/mlir/test/Dialect/SPIRV/IR/function-parameter-attributes.mlir b/mlir/test/Dialect/SPIRV/IR/function-parameter-attributes.mlir
new file mode 100644
index 00000000000000..4e69e3d9d6cebc
--- /dev/null
+++ b/mlir/test/Dialect/SPIRV/IR/function-parameter-attributes.mlir
@@ -0,0 +1,239 @@
+// RUN: mlir-opt --split-input-file --verify-diagnostics %s
+
+spirv.func @valid(%result: !spirv.ptr<i32, CrossWorkgroup> {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<Sret>]},
+    %p: !spirv.ptr<f32, CrossWorkgroup> {
+    spirv.alignment = 16 : i32,
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<NoAlias>,
+                            #spirv.function_parameter_attribute<NoCapture>]}
+) "None" {
+  spirv.Return
+}
+
+// -----
+
+// expected-error @+1 {{'spirv.alignment' must be a 32-bit signless integer attribute}}
+spirv.func @alignment_type(%p: !spirv.ptr<f32, CrossWorkgroup> {
+    spirv.alignment = 16 : i64}) "None" {
+  spirv.Return
+}
+
+// -----
+
+// expected-error @+1 {{'spirv.alignment' must be a 32-bit signless integer attribute}}
+spirv.func @alignment_not_integer(%p: !spirv.ptr<f32, CrossWorkgroup> {
+    spirv.alignment = "16"}) "None" {
+  spirv.Return
+}
+
+// -----
+
+// expected-error @+1 {{'spirv.alignment' requires a pointer parameter}}
+spirv.func @alignment_not_pointer(%p: i32 {spirv.alignment = 16 : i32}) "None" {
+  spirv.Return
+}
+
+// -----
+
+// expected-error @+1 {{'spirv.func_param_attr' must be a non-empty array of spirv::FunctionParameterAttributeAttr}}
+spirv.func @not_an_array(%p: !spirv.ptr<f32, CrossWorkgroup> {
+    spirv.func_param_attr = #spirv.function_parameter_attribute<NoAlias>}) "None" {
+  spirv.Return
+}
+
+// -----
+
+// expected-error @+1 {{'spirv.func_param_attr' must be a non-empty array of spirv::FunctionParameterAttributeAttr}}
+spirv.func @empty_array(%p: !spirv.ptr<f32, CrossWorkgroup> {
+    spirv.func_param_attr = []}) "None" {
+  spirv.Return
+}
+
+// -----
+
+// expected-error @+1 {{'spirv.func_param_attr' must be a non-empty array of spirv::FunctionParameterAttributeAttr}}
+spirv.func @integer_element(%p: !spirv.ptr<f32, CrossWorkgroup> {
+    spirv.func_param_attr = [4 : i32]}) "None" {
+  spirv.Return
+}
+
+// -----
+
+// expected-error @+1 {{'spirv.func_param_attr' must be a non-empty array of spirv::FunctionParameterAttributeAttr}}
+spirv.func @mixed_elements(%p: !spirv.ptr<f32, CrossWorkgroup> {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<NoAlias>, 5 : i32]}) "None" {
+  spirv.Return
+}
+
+// -----
+
+// expected-error @+1 {{duplicate function parameter attribute 'NoAlias'}}
+spirv.func @duplicate(%p: !spirv.ptr<f32, CrossWorkgroup> {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<NoAlias>,
+                            #spirv.function_parameter_attribute<NoAlias>]}) "None" {
+  spirv.Return
+}
+
+// -----
+
+// expected-error @+1 {{function parameter attributes 'Zext' and 'Sext' are mutually exclusive}}
+spirv.func @conflicting_extensions(%p: i32 {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<Zext>,
+                            #spirv.function_parameter_attribute<Sext>]}) "None" {
+  spirv.Return
+}
+
+// -----
+
+// expected-error @+1 {{function parameter attribute 'Zext' requires an integer parameter}}
+spirv.func @zext_pointer(%p: !spirv.ptr<i32, CrossWorkgroup> {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<Zext>]}) "None" {
+  spirv.Return
+}
+
+// -----
+
+// expected-error @+1 {{function parameter attribute 'Sext' requires an integer parameter}}
+spirv.func @sext_float(%p: f32 {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<Sext>]}) "None" {
+  spirv.Return
+}
+
+// -----
+
+// expected-error @+1 {{function parameter attribute 'NoAlias' requires a pointer parameter}}
+spirv.func @not_pointer(%p: i32 {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<NoAlias>]}) "None" {
+  spirv.Return
+}
+
+// -----
+
+// expected-error @+1 {{function parameter attribute 'Sret' requires the first parameter}}
+spirv.func @sret_not_first(%unused: i32, %result: !spirv.ptr<i32, CrossWorkgroup> {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<Sret>]}) "None" {
+  spirv.Return
+}
+
+// -----
+
+spirv.func @unknown_enum(%p: i32 {
+    // expected-error @+2 {{expected ::mlir::spirv::FunctionParameterAttribute to be one of}}
+    // expected-error @+1 {{failed to parse SPIRV_FunctionParameterAttributeAttr parameter 'value'}}
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<Unknown>]}) "None" {
+  spirv.Return
+}
+
+// -----
+
+// expected-error @+1 {{'spirv.func_param_attr' must be a non-empty array of spirv::FunctionParameterAttributeAttr}}
+spirv.func @return_not_an_array(%v: i32) -> (i32 {
+    spirv.func_param_attr = #spirv.function_parameter_attribute<Zext>}) "None" {
+  spirv.ReturnValue %v : i32
+}
+
+// -----
+
+// expected-error @+1 {{'spirv.func_param_attr' must be a non-empty array of spirv::FunctionParameterAttributeAttr}}
+spirv.func @return_empty_array(%v: i32) -> (i32 {
+    spirv.func_param_attr = []}) "None" {
+  spirv.ReturnValue %v : i32
+}
+
+// -----
+
+// expected-error @+1 {{'spirv.func_param_attr' must be a non-empty array of spirv::FunctionParameterAttributeAttr}}
+spirv.func @return_integer_element(%v: i32) -> (i32 {
+    spirv.func_param_attr = [0 : i32]}) "None" {
+  spirv.ReturnValue %v : i32
+}
+
+// -----
+
+// expected-error @+1 {{duplicate function parameter attribute 'Zext'}}
+spirv.func @return_duplicate(%v: i32) -> (i32 {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<Zext>,
+                            #spirv.function_parameter_attribute<Zext>]}) "None" {
+  spirv.ReturnValue %v : i32
+}
+
+// -----
+
+// expected-error @+1 {{function parameter attributes 'Zext' and 'Sext' are mutually exclusive}}
+spirv.func @return_conflicting_extensions(%v: i32) -> (i32 {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<Zext>,
+                            #spirv.function_parameter_attribute<Sext>]}) "None" {
+  spirv.ReturnValue %v : i32
+}
+
+// -----
+
+// expected-error @+1 {{function parameter attribute 'Zext' requires an integer result}}
+spirv.func @return_zext_pointer(%v: !spirv.ptr<i32, CrossWorkgroup>) -> (!spirv.ptr<i32, CrossWorkgroup> {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<Zext>]}) "None" {
+  spirv.ReturnValue %v : !spirv.ptr<i32, CrossWorkgroup>
+}
+
+// -----
+
+// expected-error @+1 {{function parameter attribute 'Sext' requires an integer result}}
+spirv.func @return_sext_float(%v: f32) -> (f32 {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<Sext>]}) "None" {
+  spirv.ReturnValue %v : f32
+}
+
+// -----
+
+// expected-error @+1 {{function parameter attribute 'ByVal' is not valid on function results}}
+spirv.func @return_byval(%v: !spirv.ptr<i32, CrossWorkgroup>) -> (!spirv.ptr<i32, CrossWorkgroup> {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<ByVal>]}) "None" {
+  spirv.ReturnValue %v : !spirv.ptr<i32, CrossWorkgroup>
+}
+
+// -----
+
+// expected-error @+1 {{function parameter attribute 'Sret' is not valid on function results}}
+spirv.func @return_sret(%v: !spirv.ptr<i32, CrossWorkgroup>) -> (!spirv.ptr<i32, CrossWorkgroup> {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<Sret>]}) "None" {
+  spirv.ReturnValue %v : !spirv.ptr<i32, CrossWorkgroup>
+}
+
+// -----
+
+// expected-error @+1 {{function parameter attribute 'NoAlias' is not valid on function results}}
+spirv.func @return_noalias(%v: !spirv.ptr<i32, CrossWorkgroup>) -> (!spirv.ptr<i32, CrossWorkgroup> {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<NoAlias>]}) "None" {
+  spirv.ReturnValue %v : !spirv.ptr<i32, CrossWorkgroup>
+}
+
+// -----
+
+// expected-error @+1 {{function parameter attribute 'NoCapture' is not valid on function results}}
+spirv.func @return_nocapture(%v: !spirv.ptr<i32, CrossWorkgroup>) -> (!spirv.ptr<i32, CrossWorkgroup> {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<NoCapture>]}) "None" {
+  spirv.ReturnValue %v : !spirv.ptr<i32, CrossWorkgroup>
+}
+
+// -----
+
+// expected-error @+1 {{function parameter attribute 'NoWrite' is not valid on function results}}
+spirv.func @return_nowrite(%v: !spirv.ptr<i32, CrossWorkgroup>) -> (!spirv.ptr<i32, CrossWorkgroup> {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<NoWrite>]}) "None" {
+  spirv.ReturnValue %v : !spirv.ptr<i32, CrossWorkgroup>
+}
+
+// -----
+
+// expected-error @+1 {{function parameter attribute 'NoReadWrite' is not valid on function results}}
+spirv.func @return_noreadwrite(%v: !spirv.ptr<i32, CrossWorkgroup>) -> (!spirv.ptr<i32, CrossWorkgroup> {
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<NoReadWrite>]}) "None" {
+  spirv.ReturnValue %v : !spirv.ptr<i32, CrossWorkgroup>
+}
+
+// -----
+
+// expected-error @+1 {{found unsupported 'spirv.alignment' attribute on function result}}
+spirv.func @return_alignment(%v: !spirv.ptr<i32, CrossWorkgroup>) ->
+    (!spirv.ptr<i32, CrossWorkgroup> {spirv.alignment = 4 : i32}) "None" {
+  spirv.ReturnValue %v : !spirv.ptr<i32, CrossWorkgroup>
+}

diff  --git a/mlir/test/Target/SPIRV/function-parameter-attributes.mlir b/mlir/test/Target/SPIRV/function-parameter-attributes.mlir
new file mode 100644
index 00000000000000..3559aef561601b
--- /dev/null
+++ b/mlir/test/Target/SPIRV/function-parameter-attributes.mlir
@@ -0,0 +1,105 @@
+// RUN: mlir-translate --no-implicit-module --test-spirv-roundtrip --split-input-file %s | FileCheck %s
+// RUN: %if spirv-tools %{ rm -rf %t %}
+// RUN: %if spirv-tools %{ mkdir %t %}
+// RUN: %if spirv-tools %{ mlir-translate --no-implicit-module --serialize-spirv --split-input-file --spirv-save-validation-files-with-prefix=%t/module %s %}
+// RUN: %if spirv-tools %{ spirv-val %t %}
+
+// All core FunctionParameterAttribute enumerants, including several decorations
+// on one parameter. Alignment and existing decorations must survive together.
+spirv.module Physical64 OpenCL requires #spirv.vce<v1.0, [Addresses, Kernel, Linkage, Int8], []> {
+  // CHECK-LABEL: spirv.func @parameters(
+  // CHECK-SAME: !spirv.ptr<!spirv.struct<(i32)>, CrossWorkgroup> {spirv.func_param_attr = [#spirv.function_parameter_attribute<Sret>]}
+  // CHECK-SAME: i8 {spirv.func_param_attr = [#spirv.function_parameter_attribute<Zext>]}
+  // CHECK-SAME: i8 {spirv.func_param_attr = [#spirv.function_parameter_attribute<Sext>]}
+  // CHECK-SAME: !spirv.ptr<f32, CrossWorkgroup> {spirv.alignment = 16 : i32, spirv.decoration = #spirv.decoration<Restrict>, spirv.func_param_attr = [#spirv.function_parameter_attribute<ByVal>, #spirv.function_parameter_attribute<NoAlias>, #spirv.function_parameter_attribute<NoCapture>, #spirv.function_parameter_attribute<NoWrite>]}
+  // CHECK-SAME: !spirv.ptr<f32, CrossWorkgroup> {spirv.func_param_attr = [#spirv.function_parameter_attribute<NoReadWrite>]}
+  spirv.func @parameters(
+      %result: !spirv.ptr<!spirv.struct<(i32)>, CrossWorkgroup> {
+        spirv.func_param_attr = [#spirv.function_parameter_attribute<Sret>]},
+      %unsigned: i8 {
+        spirv.func_param_attr = [#spirv.function_parameter_attribute<Zext>]},
+      %signed: i8 {
+        spirv.func_param_attr = [#spirv.function_parameter_attribute<Sext>]},
+      %input: !spirv.ptr<f32, CrossWorkgroup> {
+        spirv.alignment = 16 : i32,
+        spirv.decoration = #spirv.decoration<Restrict>,
+        spirv.func_param_attr = [#spirv.function_parameter_attribute<ByVal>,
+                                #spirv.function_parameter_attribute<NoAlias>,
+                                #spirv.function_parameter_attribute<NoCapture>,
+                                #spirv.function_parameter_attribute<NoWrite>]},
+      %unused: !spirv.ptr<f32, CrossWorkgroup> {
+        spirv.func_param_attr = [#spirv.function_parameter_attribute<NoReadWrite>]}
+  ) "None" {
+    spirv.Return
+  }
+}
+
+// -----
+
+// Return attributes decorate the OpFunction result id, not a parameter id.
+// Preserve them on both definitions and declarations.
+spirv.module Physical64 OpenCL requires #spirv.vce<v1.0, [Addresses, Kernel, Linkage, Int8, Int16], []> {
+  // CHECK-LABEL: spirv.func @return_zext(
+  // CHECK-SAME: i8 {spirv.func_param_attr = [#spirv.function_parameter_attribute<Sext>]}
+  // CHECK-SAME: -> (i8 {spirv.func_param_attr = [#spirv.function_parameter_attribute<Zext>]})
+  spirv.func @return_zext(%v: i8 {
+      spirv.func_param_attr = [#spirv.function_parameter_attribute<Sext>]}) ->
+      (i8 {spirv.func_param_attr = [#spirv.function_parameter_attribute<Zext>]}) "None" {
+    spirv.ReturnValue %v : i8
+  }
+
+  // CHECK-LABEL: spirv.func @return_sext(
+  // CHECK-SAME: -> (i16 {spirv.func_param_attr = [#spirv.function_parameter_attribute<Sext>]})
+  spirv.func @return_sext(%v: i16) ->
+      (i16 {spirv.func_param_attr = [#spirv.function_parameter_attribute<Sext>]}) "None" {
+    spirv.ReturnValue %v : i16
+  }
+}
+
+// -----
+
+spirv.module Physical64 OpenCL requires #spirv.vce<v1.0, [Addresses, Kernel, Linkage, Int8, Int16], []> {
+  // CHECK-DAG: spirv.func @external_zext() -> (i8 {spirv.func_param_attr = [#spirv.function_parameter_attribute<Zext>]})
+  spirv.func @external_zext() ->
+      (i8 {spirv.func_param_attr = [#spirv.function_parameter_attribute<Zext>]}) "None" attributes {
+    linkage_attributes = #spirv.linkage_attributes<linkage_name = "external_zext", linkage_type = <Import>>
+  }
+
+  // CHECK-DAG: spirv.func @external_sext() -> (i16 {spirv.func_param_attr = [#spirv.function_parameter_attribute<Sext>]})
+  spirv.func @external_sext() ->
+      (i16 {spirv.func_param_attr = [#spirv.function_parameter_attribute<Sext>]}) "None" attributes {
+    linkage_attributes = #spirv.linkage_attributes<linkage_name = "external_sext", linkage_type = <Import>>
+  }
+}
+
+// -----
+
+// Alignment alone must not be rejected as an unsupported argument decoration.
+// Its literal is unsigned: the high bit must not be lost in either direction.
+// The signless i32 attribute prints the same bits using a signed spelling.
+spirv.module Physical64 OpenCL requires #spirv.vce<v1.0, [Addresses, Kernel, Linkage], []> {
+  // CHECK-LABEL: spirv.func @alignment(
+  // CHECK-SAME: !spirv.ptr<f32, CrossWorkgroup> {spirv.alignment = 4 : i32}
+  // CHECK-SAME: !spirv.ptr<f32, CrossWorkgroup> {spirv.alignment = -2147483648 : i32}
+  spirv.func @alignment(
+      %a: !spirv.ptr<f32, CrossWorkgroup> {spirv.alignment = 4 : i32},
+      %b: !spirv.ptr<f32, CrossWorkgroup> {spirv.alignment = 2147483648 : i32}
+  ) "None" {
+    spirv.Return
+  }
+}
+
+// -----
+
+// Imported function declarations carry the same parameter attributes as bodies.
+spirv.module Physical64 OpenCL requires #spirv.vce<v1.0, [Addresses, Kernel, Linkage], []> {
+  // CHECK-LABEL: spirv.func @external(
+  // CHECK-SAME: !spirv.ptr<f32, CrossWorkgroup> {spirv.alignment = 8 : i32, spirv.func_param_attr = [#spirv.function_parameter_attribute<NoWrite>, #spirv.function_parameter_attribute<NoCapture>]}
+  spirv.func @external(%p: !spirv.ptr<f32, CrossWorkgroup> {
+    spirv.alignment = 8 : i32,
+    spirv.func_param_attr = [#spirv.function_parameter_attribute<NoWrite>,
+                            #spirv.function_parameter_attribute<NoCapture>]
+  }) "None" attributes {
+    linkage_attributes = #spirv.linkage_attributes<linkage_name = "external", linkage_type = <Import>>
+  }
+}

diff  --git a/mlir/test/Target/SPIRV/global-variable.mlir b/mlir/test/Target/SPIRV/global-variable.mlir
index a425412989a83f..986e207c5ce24a 100644
--- a/mlir/test/Target/SPIRV/global-variable.mlir
+++ b/mlir/test/Target/SPIRV/global-variable.mlir
@@ -19,6 +19,16 @@ spirv.module Logical GLSL450 requires #spirv.vce<v1.0, [Shader, Linkage], []> {
 
 // -----
 
+// Alignment is also preserved on module-scope variables.
+spirv.module Physical64 OpenCL requires #spirv.vce<v1.0, [Addresses, Kernel, Linkage], []> {
+  // CHECK: spirv.GlobalVariable @aligned {alignment = 16 : i32} : !spirv.ptr<f32, CrossWorkgroup>
+  // CHECK: spirv.GlobalVariable @high_bit {alignment = -2147483648 : i32} : !spirv.ptr<f32, CrossWorkgroup>
+  spirv.GlobalVariable @aligned {alignment = 16 : i32} : !spirv.ptr<f32, CrossWorkgroup>
+  spirv.GlobalVariable @high_bit {alignment = 2147483648 : i32} : !spirv.ptr<f32, CrossWorkgroup>
+}
+
+// -----
+
 spirv.module Logical GLSL450 requires #spirv.vce<v1.0, [Shader, Linkage, Int8], []> {
   // CHECK:         spirv.SpecConstant @sc = 1 : i8
   // CHECK-NEXT:    spirv.GlobalVariable @var initializer(@sc) : !spirv.ptr<i8, Uniform>

diff  --git a/mlir/test/Target/SPIRV/memory-ops.mlir b/mlir/test/Target/SPIRV/memory-ops.mlir
index 91c5ccdf784645..9641d7e8ec6ed4 100644
--- a/mlir/test/Target/SPIRV/memory-ops.mlir
+++ b/mlir/test/Target/SPIRV/memory-ops.mlir
@@ -5,6 +5,21 @@
 // RUN: %if spirv-tools %{ mlir-translate --no-implicit-module --serialize-spirv --split-input-file --spirv-save-validation-files-with-prefix=%t/module %s %}
 // RUN: %if spirv-tools %{ spirv-val %t %}
 
+// Alignment decorations on function-local variables survive round-tripping,
+// including unsigned literals whose high bit is set.
+spirv.module Physical64 OpenCL requires #spirv.vce<v1.0, [Addresses, Kernel, Linkage], []> {
+  // CHECK-LABEL: spirv.func @aligned_variables()
+  spirv.func @aligned_variables() "None" {
+    // CHECK-NEXT: %{{.*}} = spirv.Variable {alignment = 16 : i32} : !spirv.ptr<f32, Function>
+    // CHECK-NEXT: %{{.*}} = spirv.Variable {alignment = -2147483648 : i32} : !spirv.ptr<f32, Function>
+    %aligned = spirv.Variable {alignment = 16 : i32} : !spirv.ptr<f32, Function>
+    %high_bit = spirv.Variable {alignment = 2147483648 : i32} : !spirv.ptr<f32, Function>
+    spirv.Return
+  }
+}
+
+// -----
+
 spirv.module Logical GLSL450 requires #spirv.vce<v1.0, [Shader, Linkage], []> {
   // CHECK-LABEL: spirv.func @load_store
   //  CHECK-SAME: ([[ARG1:%.*]]: !spirv.ptr<f32, Input>, [[ARG2:%.*]]: !spirv.ptr<f32, Output>)

diff  --git a/mlir/unittests/Dialect/SPIRV/DeserializationTest.cpp b/mlir/unittests/Dialect/SPIRV/DeserializationTest.cpp
index dd9fddc527fc66..db19eb9e222c1b 100644
--- a/mlir/unittests/Dialect/SPIRV/DeserializationTest.cpp
+++ b/mlir/unittests/Dialect/SPIRV/DeserializationTest.cpp
@@ -177,6 +177,151 @@ TEST_F(DeserializationTest, InsufficientWordFailure) {
   expectDiagnostic("insufficient words for the last instruction");
 }
 
+TEST_F(DeserializationTest, FunctionParameterAttributeMissingOperand) {
+  addHeader();
+  addInstruction(spirv::Opcode::OpDecorate,
+                 {1, static_cast<uint32_t>(spirv::Decoration::FuncParamAttr)});
+  ASSERT_FALSE(deserialize());
+  expectDiagnostic("OpDecorate with FuncParamAttr needs a single function "
+                   "parameter attribute");
+}
+
+TEST_F(DeserializationTest, FunctionParameterAttributeExcessOperands) {
+  addHeader();
+  addInstruction(
+      spirv::Opcode::OpDecorate,
+      {1, static_cast<uint32_t>(spirv::Decoration::FuncParamAttr),
+       static_cast<uint32_t>(spirv::FunctionParameterAttribute::NoAlias),
+       static_cast<uint32_t>(spirv::FunctionParameterAttribute::NoCapture)});
+  ASSERT_FALSE(deserialize());
+  expectDiagnostic("OpDecorate with FuncParamAttr needs a single function "
+                   "parameter attribute");
+}
+
+TEST_F(DeserializationTest, FunctionParameterAttributeUnknownValue) {
+  addHeader();
+  addInstruction(
+      spirv::Opcode::OpDecorate,
+      {1, static_cast<uint32_t>(spirv::Decoration::FuncParamAttr), 100});
+  ASSERT_FALSE(deserialize());
+  expectDiagnostic("invalid FunctionParameterAttribute code: 100");
+}
+
+TEST_F(DeserializationTest, FunctionReturnAttributeOnVoid) {
+  addHeader();
+  auto fnID = nextID++;
+  addInstruction(
+      spirv::Opcode::OpDecorate,
+      {fnID, static_cast<uint32_t>(spirv::Decoration::FuncParamAttr),
+       static_cast<uint32_t>(spirv::FunctionParameterAttribute::Zext)});
+  auto voidType = addVoidType();
+  auto fnType = addFunctionType(voidType, {});
+  addInstruction(spirv::Opcode::OpFunction,
+                 {voidType, fnID,
+                  static_cast<uint32_t>(spirv::FunctionControl::None), fnType});
+  addInstruction(spirv::Opcode::OpLabel, {nextID++});
+  addInstruction(spirv::Opcode::OpReturn, {});
+  addFunctionEnd();
+
+  ASSERT_FALSE(deserialize());
+  expectDiagnostic(
+      "FuncParamAttr decoration requires a non-void function result");
+}
+
+TEST_F(DeserializationTest, FunctionParameterAttributeOnGlobalUnsupported) {
+  addHeader();
+  auto varID = nextID++;
+  addInstruction(
+      spirv::Opcode::OpDecorate,
+      {varID, static_cast<uint32_t>(spirv::Decoration::FuncParamAttr),
+       static_cast<uint32_t>(spirv::FunctionParameterAttribute::NoAlias)});
+  auto intType = addIntType(32);
+  auto ptrType = nextID++;
+  auto storageClass =
+      static_cast<uint32_t>(spirv::StorageClass::CrossWorkgroup);
+  addInstruction(spirv::Opcode::OpTypePointer,
+                 {ptrType, storageClass, intType});
+  addInstruction(spirv::Opcode::OpVariable, {ptrType, varID, storageClass});
+
+  ASSERT_FALSE(deserialize());
+  expectDiagnostic(
+      "FuncParamAttr decoration on result <id> 1 is not supported: "
+      "expected OpFunctionParameter or OpFunction");
+}
+
+TEST_F(DeserializationTest, FunctionParameterAttributeUnknownTarget) {
+  addHeader();
+  addInstruction(
+      spirv::Opcode::OpDecorate,
+      {42, static_cast<uint32_t>(spirv::Decoration::FuncParamAttr),
+       static_cast<uint32_t>(spirv::FunctionParameterAttribute::NoAlias)});
+
+  ASSERT_FALSE(deserialize());
+  expectDiagnostic(
+      "FuncParamAttr decoration on result <id> 42 is not supported: "
+      "expected OpFunctionParameter or OpFunction");
+}
+
+class UnsupportedParameterDecorationTest
+    : public DeserializationTest,
+      public ::testing::WithParamInterface<spirv::Decoration> {};
+
+TEST_P(UnsupportedParameterDecorationTest, RejectWithSupportedDecoration) {
+  addHeader();
+  auto argID = nextID++;
+  SmallVector<uint32_t> operands = {argID, static_cast<uint32_t>(GetParam())};
+  if (GetParam() == spirv::Decoration::Alignment)
+    operands.push_back(16);
+  else if (GetParam() == spirv::Decoration::FuncParamAttr)
+    operands.push_back(
+        static_cast<uint32_t>(spirv::FunctionParameterAttribute::NoWrite));
+  addInstruction(spirv::Opcode::OpDecorate, operands);
+  addInstruction(spirv::Opcode::OpDecorate,
+                 {argID, static_cast<uint32_t>(spirv::Decoration::Constant)});
+
+  auto voidType = addVoidType();
+  auto intType = addIntType(32);
+  auto ptrType = nextID++;
+  addInstruction(spirv::Opcode::OpTypePointer,
+                 {ptrType,
+                  static_cast<uint32_t>(spirv::StorageClass::CrossWorkgroup),
+                  intType});
+  auto fnType = addFunctionType(voidType, {ptrType});
+  addFunction(voidType, fnType);
+  addInstruction(spirv::Opcode::OpFunctionParameter, {ptrType, argID});
+  addInstruction(spirv::Opcode::OpLabel, {nextID++});
+  addReturn();
+  addFunctionEnd();
+
+  ASSERT_FALSE(deserialize());
+  expectDiagnostic("unhandled decoration 'constant' on function argument with "
+                   "result <id> 1");
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    SPIRV, UnsupportedParameterDecorationTest,
+    ::testing::Values(spirv::Decoration::Alignment,
+                      spirv::Decoration::FuncParamAttr,
+                      spirv::Decoration::Restrict,
+                      spirv::Decoration::RelaxedPrecision));
+
+TEST_F(DeserializationTest, AlignmentMissingOperand) {
+  addHeader();
+  addInstruction(spirv::Opcode::OpDecorate,
+                 {1, static_cast<uint32_t>(spirv::Decoration::Alignment)});
+  ASSERT_FALSE(deserialize());
+  expectDiagnostic("OpDecorate with Alignment needs a single integer literal");
+}
+
+TEST_F(DeserializationTest, AlignmentExcessOperands) {
+  addHeader();
+  addInstruction(
+      spirv::Opcode::OpDecorate,
+      {1, static_cast<uint32_t>(spirv::Decoration::Alignment), 4, 8});
+  ASSERT_FALSE(deserialize());
+  expectDiagnostic("OpDecorate with Alignment needs a single integer literal");
+}
+
 TEST_F(DeserializationTest, OpExtInstMissingOperands) {
   addHeader();
   addInstruction(spirv::Opcode::OpExtInst, {1, 2});


        


More information about the Mlir-commits mailing list