This fixes all the places in MLIR that hit the new assertion added in #106524, in preparation for enabling it by default. That is, cases where the value passed to the APInt constructor is not an N-bit signed/unsigned integer, where N is the bit width and signedness is determined by the isSigned flag. The fixes either set the correct value for isSigned, or set the implicitTrunc flag to retain the old behavior. I've left TODOs for the latter case in some places, where I think that it may be worthwhile to stop doing implicit truncation in the future. Note that the assertion is currently still disabled by default, so this patch is mostly NFC. This is just the MLIR changes split off from https://github.com/llvm/llvm-project/pull/80309.
232 lines
8.8 KiB
C++
232 lines
8.8 KiB
C++
//===- SerializationTest.cpp - SPIR-V Serialization Tests -----------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file contains corner case tests for the SPIR-V serializer that are not
|
|
// covered by normal serialization and deserialization roundtripping.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "mlir/Target/SPIRV/Serialization.h"
|
|
#include "mlir/Dialect/SPIRV/IR/SPIRVAttributes.h"
|
|
#include "mlir/Dialect/SPIRV/IR/SPIRVDialect.h"
|
|
#include "mlir/Dialect/SPIRV/IR/SPIRVEnums.h"
|
|
#include "mlir/Dialect/SPIRV/IR/SPIRVOps.h"
|
|
#include "mlir/Dialect/SPIRV/IR/SPIRVTypes.h"
|
|
#include "mlir/IR/Builders.h"
|
|
#include "mlir/IR/Location.h"
|
|
#include "mlir/IR/MLIRContext.h"
|
|
#include "mlir/Target/SPIRV/SPIRVBinaryUtils.h"
|
|
#include "llvm/ADT/DenseSet.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/Sequence.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "gmock/gmock.h"
|
|
|
|
using namespace mlir;
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Test Fixture
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
class SerializationTest : public ::testing::Test {
|
|
protected:
|
|
SerializationTest() {
|
|
context.getOrLoadDialect<mlir::spirv::SPIRVDialect>();
|
|
initModuleOp();
|
|
}
|
|
|
|
/// Initializes an empty SPIR-V module op.
|
|
void initModuleOp() {
|
|
OpBuilder builder(&context);
|
|
OperationState state(UnknownLoc::get(&context),
|
|
spirv::ModuleOp::getOperationName());
|
|
state.addAttribute("addressing_model",
|
|
builder.getAttr<spirv::AddressingModelAttr>(
|
|
spirv::AddressingModel::Logical));
|
|
state.addAttribute("memory_model", builder.getAttr<spirv::MemoryModelAttr>(
|
|
spirv::MemoryModel::GLSL450));
|
|
state.addAttribute("vce_triple",
|
|
spirv::VerCapExtAttr::get(
|
|
spirv::Version::V_1_0, ArrayRef<spirv::Capability>(),
|
|
ArrayRef<spirv::Extension>(), &context));
|
|
spirv::ModuleOp::build(builder, state);
|
|
module = cast<spirv::ModuleOp>(Operation::create(state));
|
|
}
|
|
|
|
/// Gets the `struct { float }` type.
|
|
spirv::StructType getFloatStructType() {
|
|
OpBuilder builder(module->getRegion());
|
|
llvm::SmallVector<Type, 1> elementTypes{builder.getF32Type()};
|
|
llvm::SmallVector<spirv::StructType::OffsetInfo, 1> offsetInfo{0};
|
|
return spirv::StructType::get(elementTypes, offsetInfo);
|
|
}
|
|
|
|
/// Inserts a global variable of the given `type` and `name`.
|
|
spirv::GlobalVariableOp addGlobalVar(Type type, llvm::StringRef name) {
|
|
OpBuilder builder(module->getRegion());
|
|
auto ptrType = spirv::PointerType::get(type, spirv::StorageClass::Uniform);
|
|
return builder.create<spirv::GlobalVariableOp>(
|
|
UnknownLoc::get(&context), TypeAttr::get(ptrType),
|
|
builder.getStringAttr(name), nullptr);
|
|
}
|
|
|
|
// Inserts an Integer or a Vector of Integers constant of value 'val'.
|
|
spirv::ConstantOp addConstInt(Type type, const APInt &val) {
|
|
OpBuilder builder(module->getRegion());
|
|
auto loc = UnknownLoc::get(&context);
|
|
|
|
if (auto intType = dyn_cast<IntegerType>(type)) {
|
|
return builder.create<spirv::ConstantOp>(
|
|
loc, type, builder.getIntegerAttr(type, val));
|
|
}
|
|
if (auto vectorType = dyn_cast<VectorType>(type)) {
|
|
Type elemType = vectorType.getElementType();
|
|
if (auto intType = dyn_cast<IntegerType>(elemType)) {
|
|
return builder.create<spirv::ConstantOp>(
|
|
loc, type,
|
|
DenseElementsAttr::get(vectorType,
|
|
IntegerAttr::get(elemType, val).getValue()));
|
|
}
|
|
}
|
|
llvm_unreachable("unimplemented types for AddConstInt()");
|
|
}
|
|
|
|
/// Handles a SPIR-V instruction with the given `opcode` and `operand`.
|
|
/// Returns true to interrupt.
|
|
using HandleFn = llvm::function_ref<bool(spirv::Opcode opcode,
|
|
ArrayRef<uint32_t> operands)>;
|
|
|
|
/// Returns true if we can find a matching instruction in the SPIR-V blob.
|
|
bool scanInstruction(HandleFn handleFn) {
|
|
auto binarySize = binary.size();
|
|
auto *begin = binary.begin();
|
|
auto currOffset = spirv::kHeaderWordCount;
|
|
|
|
while (currOffset < binarySize) {
|
|
auto wordCount = binary[currOffset] >> 16;
|
|
if (!wordCount || (currOffset + wordCount > binarySize))
|
|
return false;
|
|
|
|
spirv::Opcode opcode =
|
|
static_cast<spirv::Opcode>(binary[currOffset] & 0xffff);
|
|
llvm::ArrayRef<uint32_t> operands(begin + currOffset + 1,
|
|
begin + currOffset + wordCount);
|
|
if (handleFn(opcode, operands))
|
|
return true;
|
|
|
|
currOffset += wordCount;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected:
|
|
MLIRContext context;
|
|
OwningOpRef<spirv::ModuleOp> module;
|
|
SmallVector<uint32_t, 0> binary;
|
|
};
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Block decoration
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
TEST_F(SerializationTest, ContainsBlockDecoration) {
|
|
auto structType = getFloatStructType();
|
|
addGlobalVar(structType, "var0");
|
|
|
|
ASSERT_TRUE(succeeded(spirv::serialize(module.get(), binary)));
|
|
|
|
auto hasBlockDecoration = [](spirv::Opcode opcode,
|
|
ArrayRef<uint32_t> operands) {
|
|
return opcode == spirv::Opcode::OpDecorate && operands.size() == 2 &&
|
|
operands[1] == static_cast<uint32_t>(spirv::Decoration::Block);
|
|
};
|
|
EXPECT_TRUE(scanInstruction(hasBlockDecoration));
|
|
}
|
|
|
|
TEST_F(SerializationTest, ContainsNoDuplicatedBlockDecoration) {
|
|
auto structType = getFloatStructType();
|
|
// Two global variables using the same type should not decorate the type with
|
|
// duplicated `Block` decorations.
|
|
addGlobalVar(structType, "var0");
|
|
addGlobalVar(structType, "var1");
|
|
|
|
ASSERT_TRUE(succeeded(spirv::serialize(module.get(), binary)));
|
|
|
|
unsigned count = 0;
|
|
auto countBlockDecoration = [&count](spirv::Opcode opcode,
|
|
ArrayRef<uint32_t> operands) {
|
|
if (opcode == spirv::Opcode::OpDecorate && operands.size() == 2 &&
|
|
operands[1] == static_cast<uint32_t>(spirv::Decoration::Block))
|
|
++count;
|
|
return false;
|
|
};
|
|
ASSERT_FALSE(scanInstruction(countBlockDecoration));
|
|
EXPECT_EQ(count, 1u);
|
|
}
|
|
|
|
TEST_F(SerializationTest, SignlessVsSignedIntegerConstantBitExtension) {
|
|
|
|
auto signlessInt16Type =
|
|
IntegerType::get(&context, 16, IntegerType::Signless);
|
|
auto signedInt16Type = IntegerType::get(&context, 16, IntegerType::Signed);
|
|
// Check the bit extension of same value under different signedness semantics.
|
|
APInt signlessIntConstVal(signlessInt16Type.getWidth(), 0xffff,
|
|
signlessInt16Type.getSignedness());
|
|
APInt signedIntConstVal(signedInt16Type.getWidth(), -1,
|
|
signedInt16Type.getSignedness());
|
|
|
|
addConstInt(signlessInt16Type, signlessIntConstVal);
|
|
addConstInt(signedInt16Type, signedIntConstVal);
|
|
ASSERT_TRUE(succeeded(spirv::serialize(module.get(), binary)));
|
|
|
|
auto hasSignlessVal = [&](spirv::Opcode opcode, ArrayRef<uint32_t> operands) {
|
|
return opcode == spirv::Opcode::OpConstant && operands.size() == 3 &&
|
|
operands[2] == 65535;
|
|
};
|
|
EXPECT_TRUE(scanInstruction(hasSignlessVal));
|
|
|
|
auto hasSignedVal = [&](spirv::Opcode opcode, ArrayRef<uint32_t> operands) {
|
|
return opcode == spirv::Opcode::OpConstant && operands.size() == 3 &&
|
|
operands[2] == 4294967295;
|
|
};
|
|
EXPECT_TRUE(scanInstruction(hasSignedVal));
|
|
}
|
|
|
|
TEST_F(SerializationTest, ContainsSymbolName) {
|
|
auto structType = getFloatStructType();
|
|
addGlobalVar(structType, "var0");
|
|
|
|
spirv::SerializationOptions options;
|
|
options.emitSymbolName = true;
|
|
ASSERT_TRUE(succeeded(spirv::serialize(module.get(), binary, options)));
|
|
|
|
auto hasVarName = [](spirv::Opcode opcode, ArrayRef<uint32_t> operands) {
|
|
unsigned index = 1; // Skip the result <id>
|
|
return opcode == spirv::Opcode::OpName &&
|
|
spirv::decodeStringLiteral(operands, index) == "var0";
|
|
};
|
|
EXPECT_TRUE(scanInstruction(hasVarName));
|
|
}
|
|
|
|
TEST_F(SerializationTest, DoesNotContainSymbolName) {
|
|
auto structType = getFloatStructType();
|
|
addGlobalVar(structType, "var0");
|
|
|
|
spirv::SerializationOptions options;
|
|
options.emitSymbolName = false;
|
|
ASSERT_TRUE(succeeded(spirv::serialize(module.get(), binary, options)));
|
|
|
|
auto hasVarName = [](spirv::Opcode opcode, ArrayRef<uint32_t> operands) {
|
|
unsigned index = 1; // Skip the result <id>
|
|
return opcode == spirv::Opcode::OpName &&
|
|
spirv::decodeStringLiteral(operands, index) == "var0";
|
|
};
|
|
EXPECT_FALSE(scanInstruction(hasVarName));
|
|
}
|