[Mlir-commits] [mlir] [mlir][SPIRV] Support Alignment and FuncParamAttr on parameters (PR #231069)
Alexander Shaposhnikov
llvmlistbot at llvm.org
Sun Oct 11 22:48:01 PDT 2026
https://github.com/alexander-shaposhnikov created https://github.com/llvm/llvm-project/pull/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.
>From b8de343a71d264983e446e54db61eca6b10ece1f 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] [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 | 34 +++++
.../mlir/Dialect/SPIRV/IR/SPIRVBase.td | 48 ++++++
mlir/lib/Dialect/SPIRV/IR/SPIRVDialect.cpp | 64 ++++++++
.../SPIRV/Deserialization/Deserializer.cpp | 55 +++++--
.../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 | 144 ++++++++++++++++++
11 files changed, 558 insertions(+), 12 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..0780501da73d1 100644
--- a/mlir/docs/Dialects/SPIR-V.md
+++ b/mlir/docs/Dialects/SPIR-V.md
@@ -242,6 +242,40 @@ 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`: an `i32` integer attribute holding the unsigned 32-bit
+ `Alignment` literal. The parameter must have a SPIR-V pointer type.
+* `spirv.func_param_attr`: a non-empty array of
+ `#spirv.function_parameter_attribute<...>` enum attributes. Each element
+ represents one `FuncParamAttr` decoration. The supported 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; deserialization collects them without dropping other parameter
+decorations. As required by the [SPIR-V specification](
+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 pointer-only attributes require a pointer parameter,
+and `Sret` additionally requires the first parameter.
+
## 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..7425a4ab57c10 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> ")
+ return emitError(unknownLoc, "unhandled decoration '")
+ << decAttr.getName() << "' 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..ec1df425471f8 100644
--- a/mlir/unittests/Dialect/SPIRV/DeserializationTest.cpp
+++ b/mlir/unittests/Dialect/SPIRV/DeserializationTest.cpp
@@ -177,6 +177,150 @@ 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});
More information about the Mlir-commits
mailing list