[Mlir-commits] [mlir] [mlir][SPIRV] Support Alignment and FuncParamAttr on parameters (PR #231069)
Alexander Shaposhnikov
llvmlistbot at llvm.org
Sun Oct 11 23:33:49 PDT 2026
https://github.com/alexander-shaposhnikov updated https://github.com/llvm/llvm-project/pull/231069
>From ec65a7c74829a79f607a09341832e442078e3557 Mon Sep 17 00:00:00 2001
From: Alexander Shaposhnikov <alexander.v.shaposhnikov at gmail.com>
Date: Sun, 11 Oct 2026 22:37:24 -0700
Subject: [PATCH 1/2] [mlir][SPIRV] Support Alignment and FuncParamAttr on
parameters
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.
---
mlir/docs/Dialects/SPIR-V.md | 48 ++++++
.../mlir/Dialect/SPIRV/IR/SPIRVBase.td | 48 ++++++
mlir/lib/Dialect/SPIRV/IR/SPIRVDialect.cpp | 64 ++++++++
.../SPIRV/Deserialization/Deserializer.cpp | 57 +++++--
.../SPIRV/Deserialization/Deserializer.h | 5 +
.../SPIRV/Serialization/SerializeOps.cpp | 10 ++
.../Target/SPIRV/Serialization/Serializer.cpp | 8 +
.../IR/function-parameter-attributes.mlir | 125 +++++++++++++++
.../SPIRV/function-parameter-attributes.mlir | 67 ++++++++
mlir/test/Target/SPIRV/global-variable.mlir | 10 ++
.../Dialect/SPIRV/DeserializationTest.cpp | 148 ++++++++++++++++++
11 files changed, 577 insertions(+), 13 deletions(-)
create mode 100644 mlir/test/Dialect/SPIRV/IR/function-parameter-attributes.mlir
create mode 100644 mlir/test/Target/SPIRV/function-parameter-attributes.mlir
diff --git a/mlir/docs/Dialects/SPIR-V.md b/mlir/docs/Dialects/SPIR-V.md
index dd68e6ec8b7b8..705f4633bd8da 100644
--- a/mlir/docs/Dialects/SPIR-V.md
+++ b/mlir/docs/Dialects/SPIR-V.md
@@ -242,6 +242,54 @@ 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 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 function return values is not yet supported and is diagnosed,
+as is any other target that is not an `OpFunctionParameter`.
+
+As required by 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),
+duplicate function parameter attributes and the combination of `Zext` with
+`Sext` are invalid. The dialect requires integer parameters for `Zext` and
+`Sext`. The other supported attributes require pointer parameters, and `Sret`
+additionally requires the first parameter.
+
+`Alignment` is also preserved on module-scope variables, using an unprefixed
+`alignment` attribute on `spirv.GlobalVariable`:
+
+```mlir
+spirv.GlobalVariable @buffer {alignment = 16 : i32} : !spirv.ptr<f32, CrossWorkgroup>
+```
+
## 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 a8073d7c848d6..d52b9710b8b59 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/lib/Dialect/SPIRV/IR/SPIRVDialect.cpp b/mlir/lib/Dialect/SPIRV/IR/SPIRVDialect.cpp
index 2ab675221bd11..fc3b1531e417a 100644
--- a/mlir/lib/Dialect/SPIRV/IR/SPIRVDialect.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/SPIRVDialect.cpp
@@ -1097,6 +1097,56 @@ static LogicalResult verifyRegionAttribute(Location loc, Type valueType,
<< symbol << "' attribute on region argument";
}
+/// Verifies the FuncParamAttr decorations of a function parameter.
+static LogicalResult verifyFunctionParameterAttributes(Location loc, Type type,
+ unsigned index,
+ Attribute attr) {
+ auto attrs = dyn_cast<ArrayAttr>(attr);
+ if (!attrs || attrs.empty() ||
+ !llvm::all_of(attrs,
+ llvm::IsaPred<spirv::FunctionParameterAttributeAttr>))
+ return emitError(loc,
+ "'spirv.func_param_attr' 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 parameter";
+ hasZext |= value == spirv::FunctionParameterAttribute::Zext;
+ hasSext |= value == spirv::FunctionParameterAttribute::Sext;
+ break;
+ default:
+ 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,6 +1156,20 @@ LogicalResult SPIRVDialect::verifyRegionArgAttribute(Operation *op,
return success();
Type argType = funcOp.getArgumentTypes()[argIndex];
+ if (attribute.getName() == "spirv.alignment") {
+ auto alignment = dyn_cast<IntegerAttr>(attribute.getValue());
+ if (!alignment || !alignment.getType().isSignlessInteger(32))
+ return op->emitError(
+ "'spirv.alignment' must be a 32-bit signless integer "
+ "attribute");
+ if (!isa<spirv::PointerType>(argType))
+ return op->emitError("'spirv.alignment' requires a pointer parameter");
+ return success();
+ }
+ if (attribute.getName() == "spirv.func_param_attr")
+ return verifyFunctionParameterAttributes(op->getLoc(), argType, argIndex,
+ attribute.getValue());
+
return verifyRegionAttribute(op->getLoc(), argType, attribute);
}
diff --git a/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp b/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
index 9df544d861767..3d06e82c0f9b5 100644
--- a/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
+++ b/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
@@ -105,6 +105,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";
+
attachVCETriple();
LLVM_DEBUG(logger.startLine()
@@ -290,6 +295,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:
@@ -485,13 +508,18 @@ 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;
+ NamedAttrList attributes;
+ if (auto attrs = functionParameterAttrs.lookup(argID)) {
+ attributes.set("spirv.func_param_attr", attrs);
+ functionParameterAttrs.erase(argID);
+ }
for (NamedAttribute decAttr : decorations[argID]) {
+ if (decAttr.getName() == "alignment") {
+ attributes.set("spirv.alignment", decAttr.getValue());
+ continue;
+ }
+ bool handled = false;
for (auto decoration :
{spirv::Decoration::Aliased, spirv::Decoration::Restrict,
spirv::Decoration::AliasedPointer,
@@ -508,8 +536,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))) {
@@ -524,17 +555,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();
}
diff --git a/mlir/lib/Target/SPIRV/Deserialization/Deserializer.h b/mlir/lib/Target/SPIRV/Deserialization/Deserializer.h
index d2b7dbaad4655..5d92b01868e3d 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. 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 442064fcb6fe5..b13f45b703272 100644
--- a/mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp
+++ b/mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp
@@ -268,6 +268,16 @@ 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.alignment" ||
+ argAttr.getName() == "spirv.func_param_attr") {
+ auto decoration = argAttr.getName() == "spirv.alignment"
+ ? Decoration::Alignment
+ : Decoration::FuncParamAttr;
+ if (failed(processDecorationAttr(op.getLoc(), argValueID, decoration,
+ argAttr.getValue())))
+ return failure();
+ continue;
+ }
if (argAttr.getName() != DecorationAttr::name)
continue;
diff --git a/mlir/lib/Target/SPIRV/Serialization/Serializer.cpp b/mlir/lib/Target/SPIRV/Serialization/Serializer.cpp
index 3919c3ed5ec36..53284a4df893b 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 0000000000000..905d0add43dc0
--- /dev/null
+++ b/mlir/test/Dialect/SPIRV/IR/function-parameter-attributes.mlir
@@ -0,0 +1,125 @@
+// 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
+}
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 0000000000000..fc8a95029fa33
--- /dev/null
+++ b/mlir/test/Target/SPIRV/function-parameter-attributes.mlir
@@ -0,0 +1,67 @@
+// 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
+ }
+}
+
+// -----
+
+// 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 a425412989a83..986e207c5ce24 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/unittests/Dialect/SPIRV/DeserializationTest.cpp b/mlir/unittests/Dialect/SPIRV/DeserializationTest.cpp
index dd9fddc527fc6..aaad50feed41e 100644
--- a/mlir/unittests/Dialect/SPIRV/DeserializationTest.cpp
+++ b/mlir/unittests/Dialect/SPIRV/DeserializationTest.cpp
@@ -177,6 +177,154 @@ 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, FunctionReturnAttributeUnsupported) {
+ addHeader();
+ auto fnID = nextID++;
+ addInstruction(
+ spirv::Opcode::OpDecorate,
+ {fnID, static_cast<uint32_t>(spirv::Decoration::FuncParamAttr),
+ static_cast<uint32_t>(spirv::FunctionParameterAttribute::Zext)});
+ auto intType = addIntType(32);
+ auto zeroID = nextID++;
+ addInstruction(spirv::Opcode::OpConstant, {intType, zeroID, 0});
+ auto fnType = addFunctionType(intType, {});
+ addInstruction(spirv::Opcode::OpFunction,
+ {intType, fnID,
+ static_cast<uint32_t>(spirv::FunctionControl::None), fnType});
+ addInstruction(spirv::Opcode::OpLabel, {nextID++});
+ addInstruction(spirv::Opcode::OpReturnValue, {zeroID});
+ addFunctionEnd();
+
+ ASSERT_FALSE(deserialize());
+ expectDiagnostic(
+ "FuncParamAttr decoration on result <id> 1 is not supported: "
+ "expected OpFunctionParameter");
+}
+
+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");
+}
+
+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");
+}
+
+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});
>From 437f027f7eefb7ff230bf81d7e9b87ab06ea0a00 Mon Sep 17 00:00:00 2001
From: Alexander Shaposhnikov <alexander.v.shaposhnikov at gmail.com>
Date: Sun, 11 Oct 2026 23:30:26 -0700
Subject: [PATCH 2/2] Add more tests
---
mlir/docs/Dialects/SPIR-V.md | 41 +++++--
mlir/lib/Dialect/SPIRV/IR/SPIRVDialect.cpp | 31 +++--
.../SPIRV/Deserialization/Deserializer.cpp | 18 ++-
.../SPIRV/Deserialization/Deserializer.h | 6 +-
.../SPIRV/Serialization/SerializeOps.cpp | 7 ++
.../IR/function-parameter-attributes.mlir | 114 ++++++++++++++++++
.../SPIRV/function-parameter-attributes.mlir | 40 ++++++
mlir/test/Target/SPIRV/memory-ops.mlir | 15 +++
.../Dialect/SPIRV/DeserializationTest.cpp | 19 ++-
9 files changed, 254 insertions(+), 37 deletions(-)
diff --git a/mlir/docs/Dialects/SPIR-V.md b/mlir/docs/Dialects/SPIR-V.md
index 705f4633bd8da..f49abd8e348d6 100644
--- a/mlir/docs/Dialects/SPIR-V.md
+++ b/mlir/docs/Dialects/SPIR-V.md
@@ -242,7 +242,7 @@ 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 decorations
+### Function parameter and return decorations
Decorations on `OpFunctionParameter` are represented by argument attributes on
`spirv.func`. In addition to `spirv.decoration`, function parameters support:
@@ -273,21 +273,40 @@ spirv.func @read(%p: !spirv.ptr<f32, CrossWorkgroup> {
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 function return values is not yet supported and is diagnosed,
-as is any other target that is not an `OpFunctionParameter`.
-As required by 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),
-duplicate function parameter attributes and the combination of `Zext` with
-`Sext` are invalid. The dialect requires integer parameters for `Zext` and
-`Sext`. The other supported attributes require pointer parameters, and `Sret`
-additionally requires the first parameter.
+`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:
-`Alignment` is also preserved on module-scope variables, using an unprefixed
-`alignment` attribute on `spirv.GlobalVariable`:
+```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
diff --git a/mlir/lib/Dialect/SPIRV/IR/SPIRVDialect.cpp b/mlir/lib/Dialect/SPIRV/IR/SPIRVDialect.cpp
index fc3b1531e417a..98390ed2970a0 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,10 +1099,11 @@ static LogicalResult verifyRegionAttribute(Location loc, Type valueType,
<< symbol << "' attribute on region argument";
}
-/// Verifies the FuncParamAttr decorations of a function parameter.
-static LogicalResult verifyFunctionParameterAttributes(Location loc, Type type,
- unsigned index,
- Attribute attr) {
+/// 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,
@@ -1125,16 +1128,20 @@ static LogicalResult verifyFunctionParameterAttributes(Location loc, Type type,
if (!isa<IntegerType>(type))
return emitError(loc, "function parameter attribute '")
<< spirv::stringifyFunctionParameterAttribute(value)
- << "' requires an integer parameter";
+ << "' 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)
+ if (value == spirv::FunctionParameterAttribute::Sret && *index != 0)
return emitError(loc,
"function parameter attribute 'Sret' requires the "
"first parameter");
@@ -1167,8 +1174,8 @@ LogicalResult SPIRVDialect::verifyRegionArgAttribute(Operation *op,
return success();
}
if (attribute.getName() == "spirv.func_param_attr")
- return verifyFunctionParameterAttributes(op->getLoc(), argType, argIndex,
- attribute.getValue());
+ return verifyFunctionParameterAttributes(op->getLoc(), argType,
+ attribute.getValue(), argIndex);
return verifyRegionAttribute(op->getLoc(), argType, attribute);
}
@@ -1176,6 +1183,14 @@ LogicalResult SPIRVDialect::verifyRegionArgAttribute(Operation *op,
LogicalResult SPIRVDialect::verifyRegionResultAttribute(
Operation *op, unsigned /*regionIndex*/, unsigned resultIndex,
NamedAttribute attribute) {
+ if (auto funcOp = dyn_cast<spirv::FuncOp>(op)) {
+ if (attribute.getName() == "spirv.func_param_attr")
+ return verifyFunctionParameterAttributes(
+ op->getLoc(), funcOp.getResultTypes()[resultIndex],
+ attribute.getValue());
+ return op->emitError("found unsupported '")
+ << attribute.getName() << "' 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/Target/SPIRV/Deserialization/Deserializer.cpp b/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
index 3d06e82c0f9b5..07d98a1d6d509 100644
--- a/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
+++ b/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
@@ -108,7 +108,7 @@ LogicalResult spirv::Deserializer::deserialize() {
if (!functionParameterAttrs.empty())
return emitError(unknownLoc, "FuncParamAttr decoration on result <id> ")
<< functionParameterAttrs.begin()->first
- << " is not supported: expected OpFunctionParameter";
+ << " is not supported: expected OpFunctionParameter or OpFunction";
attachVCETriple();
@@ -510,9 +510,10 @@ LogicalResult spirv::Deserializer::setFunctionArgAttrs(
uint32_t argID, SmallVectorImpl<Attribute> &argAttrs, size_t argIndex) {
spirv::DecorationAttr foundDecorationAttr;
NamedAttrList attributes;
- if (auto attrs = functionParameterAttrs.lookup(argID)) {
- attributes.set("spirv.func_param_attr", attrs);
- functionParameterAttrs.erase(argID);
+ if (auto it = functionParameterAttrs.find(argID);
+ it != functionParameterAttrs.end()) {
+ attributes.set("spirv.func_param_attr", it->second);
+ functionParameterAttrs.erase(it);
}
for (NamedAttribute decAttr : decorations[argID]) {
if (decAttr.getName() == "alignment") {
@@ -612,6 +613,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.func_param_attr", 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 5d92b01868e3d..543db3c99bddb 100644
--- a/mlir/lib/Target/SPIRV/Deserialization/Deserializer.h
+++ b/mlir/lib/Target/SPIRV/Deserialization/Deserializer.h
@@ -706,9 +706,9 @@ class Deserializer {
// Result <id> to decorations mapping.
DenseMap<uint32_t, NamedAttrList> decorations;
- // FuncParamAttr decorations awaiting an OpFunctionParameter. Keeping them
- // separate prevents unsupported targets from acquiring discardable attributes
- // that would be silently dropped during serialization.
+ // 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
diff --git a/mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp b/mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp
index b13f45b703272..33346080afb40 100644
--- a/mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp
+++ b/mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp
@@ -323,6 +323,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.func_param_attr"))
+ 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/test/Dialect/SPIRV/IR/function-parameter-attributes.mlir b/mlir/test/Dialect/SPIRV/IR/function-parameter-attributes.mlir
index 905d0add43dc0..4e69e3d9d6ceb 100644
--- a/mlir/test/Dialect/SPIRV/IR/function-parameter-attributes.mlir
+++ b/mlir/test/Dialect/SPIRV/IR/function-parameter-attributes.mlir
@@ -123,3 +123,117 @@ spirv.func @unknown_enum(%p: i32 {
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
index fc8a95029fa33..6027e8a046816 100644
--- a/mlir/test/Target/SPIRV/function-parameter-attributes.mlir
+++ b/mlir/test/Target/SPIRV/function-parameter-attributes.mlir
@@ -36,6 +36,46 @@ spirv.module Physical64 OpenCL requires #spirv.vce<v1.0, [Addresses, Kernel, Lin
// -----
+// 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-LABEL: spirv.func @external_zext(
+ // CHECK-SAME: -> (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-LABEL: spirv.func @external_sext(
+ // CHECK-SAME: -> (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.
diff --git a/mlir/test/Target/SPIRV/memory-ops.mlir b/mlir/test/Target/SPIRV/memory-ops.mlir
index 91c5ccdf78464..9641d7e8ec6ed 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 aaad50feed41e..db19eb9e222c1 100644
--- a/mlir/unittests/Dialect/SPIRV/DeserializationTest.cpp
+++ b/mlir/unittests/Dialect/SPIRV/DeserializationTest.cpp
@@ -207,28 +207,25 @@ TEST_F(DeserializationTest, FunctionParameterAttributeUnknownValue) {
expectDiagnostic("invalid FunctionParameterAttribute code: 100");
}
-TEST_F(DeserializationTest, FunctionReturnAttributeUnsupported) {
+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 intType = addIntType(32);
- auto zeroID = nextID++;
- addInstruction(spirv::Opcode::OpConstant, {intType, zeroID, 0});
- auto fnType = addFunctionType(intType, {});
+ auto voidType = addVoidType();
+ auto fnType = addFunctionType(voidType, {});
addInstruction(spirv::Opcode::OpFunction,
- {intType, fnID,
+ {voidType, fnID,
static_cast<uint32_t>(spirv::FunctionControl::None), fnType});
addInstruction(spirv::Opcode::OpLabel, {nextID++});
- addInstruction(spirv::Opcode::OpReturnValue, {zeroID});
+ addInstruction(spirv::Opcode::OpReturn, {});
addFunctionEnd();
ASSERT_FALSE(deserialize());
expectDiagnostic(
- "FuncParamAttr decoration on result <id> 1 is not supported: "
- "expected OpFunctionParameter");
+ "FuncParamAttr decoration requires a non-void function result");
}
TEST_F(DeserializationTest, FunctionParameterAttributeOnGlobalUnsupported) {
@@ -249,7 +246,7 @@ TEST_F(DeserializationTest, FunctionParameterAttributeOnGlobalUnsupported) {
ASSERT_FALSE(deserialize());
expectDiagnostic(
"FuncParamAttr decoration on result <id> 1 is not supported: "
- "expected OpFunctionParameter");
+ "expected OpFunctionParameter or OpFunction");
}
TEST_F(DeserializationTest, FunctionParameterAttributeUnknownTarget) {
@@ -262,7 +259,7 @@ TEST_F(DeserializationTest, FunctionParameterAttributeUnknownTarget) {
ASSERT_FALSE(deserialize());
expectDiagnostic(
"FuncParamAttr decoration on result <id> 42 is not supported: "
- "expected OpFunctionParameter");
+ "expected OpFunctionParameter or OpFunction");
}
class UnsupportedParameterDecorationTest
More information about the Mlir-commits
mailing list