[spirv] Basic serializer and deserializer

This CL adds the basic SPIR-V serializer and deserializer for converting
SPIR-V module into the binary format and back. Right now only an empty
module with addressing model and memory model is supported; (de)serialize
other components will be added gradually with subsequent CLs.

The purpose of this library is to enable importing SPIR-V binary modules
to run transformations on them and exporting SPIR-V modules to be consumed
by execution environments. The focus is transformations, which inevitably
means changes to the binary module; so it is not designed to be a general
tool for investigating the SPIR-V binary module and does not guarantee
roundtrip equivalence (at least for now).

PiperOrigin-RevId: 254473019
This commit is contained in:
Lei Zhang
2019-06-21 14:51:58 -07:00
committed by jpienaar
parent 5508807594
commit 8f77d2afed
22 changed files with 810 additions and 2 deletions

View File

@@ -37,7 +37,8 @@ The SPIR-V dialect has the following conventions:
A SPIR-V module is defined via the `spv.module` op, which has one region that
contains one block. Model-level instructions, including function definitions,
are all placed inside the block.
are all placed inside the block. Functions are defined using the standard `func`
op.
Compared to the binary format, we adjust how certain module-level SPIR-V
instructions are represented in the SPIR-V dialect. Notably,
@@ -89,7 +90,7 @@ For example,
### Image type
This corresponds to SPIR-V [image_type][ImageType]. Its syntax is
This corresponds to SPIR-V [image type][ImageType]. Its syntax is
``` {.ebnf}
dim ::= `1D` | `2D` | `3D` | `Cube` | <and other SPIR-V Dim specifiers...>
@@ -151,8 +152,23 @@ For example,
!spv.rtarray<vector<4 x f32>>
```
## Serialization
The serialization library provides two entry points, `mlir::spirv::serialize()`
and `mlir::spirv::deserialize()`, for converting a MLIR SPIR-V module to binary
format and back.
The purpose of this library is to enable importing SPIR-V binary modules to run
transformations on them and exporting SPIR-V modules to be consumed by execution
environments. The focus is transformations, which inevitably means changes to
the binary module; so it is not designed to be a general tool for investigating
the SPIR-V binary module and does not guarantee roundtrip equivalence (at least
for now). For the latter, please use the assembler/disassembler in the
[SPIRV-Tools][SPIRV-Tools] project.
[SPIR-V]: https://www.khronos.org/registry/spir-v/
[ArrayType]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpTypeArray
[PointerType]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpTypePointer
[RuntimeArrayType]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpTypeRuntimeArray
[ImageType]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpTypeImage
[SPIRV-Tools]: https://github.com/KhronosGroup/SPIRV-Tools

View File

@@ -673,6 +673,14 @@ class EnumAttr<string name, string description, list<EnumAttrCase> cases> :
// TODO(b/134741431): use dialect to provide the namespace.
string cppNamespace = "";
// The name of the utility function that converts a value of the underlying
// type to the corresponding symbol. It will have the following signature:
//
// ```c++
// llvm::Optional<<qualified-enum-class-name>> <fn-name>(<underlying-type>);
// ```
string underlyingToSymbolFnName = "symbolize" # name;
// The name of the utility function that converts a string to the
// corresponding symbol. It will have the following signature:
//

View File

@@ -8,4 +8,8 @@ mlir_tablegen(SPIRVEnums.h.inc -gen-enum-decls)
mlir_tablegen(SPIRVEnums.cpp.inc -gen-enum-defs)
add_public_tablegen_target(MLIRSPIRVEnumsIncGen)
set(LLVM_TARGET_DEFINITIONS SPIRVOps.td)
mlir_tablegen(SPIRVSerialization.inc -gen-spirv-serial)
add_public_tablegen_target(MLIRSPIRVSerializationGen)
add_subdirectory(Transforms)

View File

@@ -290,6 +290,10 @@ def SPV_SamplerUseAttr:
// Base class for all SPIR-V ops.
class SPV_Op<string mnemonic, list<OpTrait> traits = []> :
Op<SPV_Dialect, mnemonic, traits> {
// Opcode for the binary format. Ops cannot be directly serialized will
// leave this field as unset.
int opcode = ?;
// For each SPIR-V op, the following static functions need to be defined
// in SPVOps.cpp:
//

View File

@@ -61,6 +61,8 @@ def SPV_FMulOp : SPV_Op<"FMul", [NoSideEffect, SameOperandsAndResultType]> {
// No additional verification needed in addition to the ODS-generated ones.
let verifier = [{ return success(); }];
let opcode = 133;
}
def SPV_ReturnOp : SPV_Op<"Return", [Terminator]> {
@@ -78,6 +80,8 @@ def SPV_ReturnOp : SPV_Op<"Return", [Terminator]> {
let printer = [{ printNoIOOp(getOperation(), p); }];
let verifier = [{ return verifyReturn(*this); }];
let opcode = 253;
}
def SPV_VariableOp : SPV_Op<"Variable"> {
@@ -129,6 +133,8 @@ def SPV_VariableOp : SPV_Op<"Variable"> {
let results = (outs
SPV_AnyPtr:$pointer
);
let opcode = 59;
}
#endif // SPIRV_OPS

View File

@@ -61,6 +61,8 @@ def SPV_ModuleOp : SPV_Op<"module", []> {
let results = (outs);
let regions = (region SizedRegion<1>:$body);
let builders = [OpBuilder<"Builder *, OperationState *state">];
}
def SPV_ModuleEndOp : SPV_Op<"_module_end", [Terminator]> {

View File

@@ -0,0 +1,48 @@
//===- Serialization.h - MLIR SPIR-V (De)serialization ----------*- C++ -*-===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
//
// This file declares the entry points for serialize and deserialze SPIR-V
// binary modules.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_SPIRV_SERIALIZATION_H_
#define MLIR_SPIRV_SERIALIZATION_H_
#include "mlir/Support/LLVM.h"
namespace mlir {
class MLIRContext;
namespace spirv {
class ModuleOp;
/// Serializes the given SPIR-V `module` and writes to `binary`. Returns true on
/// success; otherwise, reports errors to the error handler registered with the
/// MLIR context for `module` and returns false.
bool serialize(ModuleOp module, SmallVectorImpl<uint32_t> &binary);
/// Deserializes the given SPIR-V `binary` module and creates a MLIR ModuleOp
/// in the given `context`. Returns the ModuleOp on success; otherwise, reports
/// errors to the error handler registered with `context` and returns
/// llvm::None.
Optional<ModuleOp> deserialize(ArrayRef<uint32_t> binary, MLIRContext *context);
} // end namespace spirv
} // end namespace mlir
#endif // MLIR_SPIRV_SERIALIZATION_H_

View File

@@ -152,6 +152,10 @@ public:
// Returns the underlying type.
StringRef getUnderlyingType() const;
// Returns the name of the utility function that converts a value of the
// underlying type to the corresponding symbol.
StringRef getUnderlyingToSymbolFnName() const;
// Returns the name of the utility function that converts a string to the
// corresponding symbol.
StringRef getStringToSymbolFnName() const;

View File

@@ -18,3 +18,5 @@ target_link_libraries(MLIRSPIRV
MLIRIR
MLIRParser
MLIRSupport)
add_subdirectory(Serialization)

View File

@@ -139,6 +139,10 @@ static void ensureModuleEnd(Region *region, Builder builder, Location loc) {
block.push_back(Operation::create(state));
}
void spirv::ModuleOp::build(Builder *builder, OperationState *state) {
ensureModuleEnd(state->addRegion(), *builder, state->location);
}
static ParseResult parseModuleOp(OpAsmParser *parser, OperationState *state) {
Region *body = state->addRegion();

View File

@@ -0,0 +1,17 @@
add_llvm_library(MLIRSPIRVSerialization
ConvertFromBinary.cpp
ConvertToBinary.cpp
Deserializer.cpp
Serializer.cpp
ADDITIONAL_HEADER_DIRS
${MLIR_MAIN_INCLUDE_DIR}/mlir/SPIRV
)
add_dependencies(MLIRSPIRVSerialization
MLIRSPIRVSerializationGen)
target_link_libraries(MLIRSPIRVSerialization
MLIRIR
MLIRSPIRV
MLIRSupport)

View File

@@ -0,0 +1,98 @@
//===- ConvertFromBinary.cpp - MLIR SPIR-V binary to module conversion ----===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
//
// This file implements a translation from SPIR-V binary module to MLIR SPIR-V
// ModuleOp.
//
//===----------------------------------------------------------------------===//
#include "mlir/IR/Builders.h"
#include "mlir/IR/Module.h"
#include "mlir/SPIRV/SPIRVOps.h"
#include "mlir/SPIRV/Serialization.h"
#include "mlir/StandardOps/Ops.h"
#include "mlir/Support/FileUtilities.h"
#include "mlir/Translation.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/MemoryBuffer.h"
using namespace mlir;
// Adds a one-block function named as `spirv_module` to `module` and returns the
// block. The created block will be terminated by `std.return`.
Block *createOneBlockFunction(Builder builder, Module *module) {
auto fnType = builder.getFunctionType(/*inputs=*/{}, /*results=*/{});
auto *fn = new Function(builder.getUnknownLoc(), "spirv_module", fnType);
module->getFunctions().push_back(fn);
auto *block = new Block();
fn->push_back(block);
OperationState state(builder.getContext(), builder.getUnknownLoc(),
ReturnOp::getOperationName());
ReturnOp::build(&builder, &state);
block->push_back(Operation::create(state));
return block;
}
// Deserializes the SPIR-V binary module stored in the file named as
// `inputFilename` and returns a module containing the SPIR-V module.
std::unique_ptr<Module> deserializeModule(llvm::StringRef inputFilename,
MLIRContext *context) {
Builder builder(context);
std::string errorMessage;
auto file = openInputFile(inputFilename, &errorMessage);
if (!file) {
context->emitError(builder.getUnknownLoc()) << errorMessage;
return {};
}
// Make sure the input stream can be treated as a stream of SPIR-V words
auto start = file->getBufferStart();
auto end = file->getBufferEnd();
if ((start - end) % sizeof(uint32_t) != 0) {
context->emitError(builder.getUnknownLoc())
<< "SPIR-V binary module must contain integral number of 32-bit words";
return {};
}
auto binary = llvm::makeArrayRef(reinterpret_cast<const uint32_t *>(start),
(end - start) / sizeof(uint32_t));
auto spirvModule = spirv::deserialize(binary, context);
if (!spirvModule)
return {};
// TODO(antiagainst): due to the restriction of the current translation
// infrastructure, we must return a MLIR module here. So we are wrapping the
// converted SPIR-V ModuleOp inside a MLIR module. This should be changed to
// return the SPIR-V ModuleOp directly after module and function are migrated
// to be general ops.
std::unique_ptr<Module> module(builder.createModule());
Block *block = createOneBlockFunction(builder, module.get());
block->push_front(spirvModule->getOperation());
return module;
}
static TranslateToMLIRRegistration
registration("deserialize-spirv",
[](StringRef inputFilename, MLIRContext *context) {
return deserializeModule(inputFilename, context);
});

View File

@@ -0,0 +1,78 @@
//===- ConvertToBinary.cpp - MLIR SPIR-V module to binary conversion ------===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
//
// This file implements a translation from MLIR SPIR-V ModuleOp to SPIR-V
// binary module.
//
//===----------------------------------------------------------------------===//
#include "mlir/IR/Module.h"
#include "mlir/SPIRV/SPIRVOps.h"
#include "mlir/SPIRV/Serialization.h"
#include "mlir/Support/FileUtilities.h"
#include "mlir/Support/LogicalResult.h"
#include "mlir/Translation.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/ToolOutputFile.h"
using namespace mlir;
LogicalResult serializeModule(Module *module, StringRef outputFilename) {
if (!module)
return failure();
SmallVector<uint32_t, 0> binary;
bool done = false;
bool success = false;
// TODO(antiagainst): we are checking there is only one SPIR-V ModuleOp in
// this module and serialize it. This is due to the restriction of the current
// translation infrastructure; we must take in a MLIR module here. So we are
// wrapping the SPIR-V ModuleOp inside a MLIR module. This should be changed
// to take in the SPIR-V ModuleOp directly after module and function are
// migrated to be general ops.
for (auto &fn : *module) {
fn.walk<spirv::ModuleOp>([&](spirv::ModuleOp spirvModule) {
if (done) {
spirvModule.emitError("found more than one 'spv.module' op");
return;
}
done = true;
success = spirv::serialize(spirvModule, binary);
});
}
if (!success)
return failure();
auto file = openOutputFile(outputFilename);
if (!file)
return failure();
file->os().write(reinterpret_cast<char *>(binary.data()),
binary.size() * sizeof(uint32_t));
file->keep();
return mlir::success();
}
static TranslateFromMLIRRegistration
registration("serialize-spirv",
[](Module *module, StringRef outputFilename) {
return failed(serializeModule(module, outputFilename));
});

View File

@@ -0,0 +1,197 @@
//===- Deserializer.cpp - MLIR SPIR-V Deserialization ---------------------===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
//
// This file defines the SPIR-V binary to MLIR SPIR-V module deseralization.
//
//===----------------------------------------------------------------------===//
#include "mlir/SPIRV/Serialization.h"
#include "SPIRVBinaryUtils.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/Location.h"
#include "mlir/SPIRV/SPIRVOps.h"
#include "mlir/SPIRV/SPIRVTypes.h"
#include "mlir/Support/LogicalResult.h"
#include "llvm/ADT/SmallVector.h"
using namespace mlir;
namespace {
/// A SPIR-V module serializer.
///
/// A SPIR-V binary module is a single linear stream of instructions; each
/// instruction is composed of 32-bit words. The first word of an instruction
/// records the total number of words of that instruction using the 16
/// higher-order bits. So this deserializer uses that to get instruction
/// boundary and parse instructions and build a SPIR-V ModuleOp gradually.
///
// TODO(antiagainst): clean up created ops on errors
class Deserializer {
public:
/// Creates a deserializer for the given SPIR-V `binary` module.
/// The SPIR-V ModuleOp will be created into `context.
explicit Deserializer(ArrayRef<uint32_t> binary, MLIRContext *context);
/// Deserializes the remembered SPIR-V binary module.
LogicalResult deserialize();
/// Collects the final SPIR-V ModuleOp.
Optional<spirv::ModuleOp> collect();
private:
/// Processes SPIR-V module header.
LogicalResult processHeader();
/// Processes a SPIR-V instruction with the given `opcode` and `operands`.
LogicalResult processInstruction(uint32_t opcode,
ArrayRef<uint32_t> operands);
LogicalResult processMemoryModel(ArrayRef<uint32_t> operands);
/// Initializes the `module` ModuleOp in this deserializer instance.
spirv::ModuleOp createModuleOp();
private:
/// The SPIR-V binary module.
ArrayRef<uint32_t> binary;
/// The current word offset into the binary module.
unsigned curOffset = 0;
/// MLIRContext to create SPIR-V ModuleOp into.
MLIRContext *context;
// TODO(antiagainst): create Location subclass for binary blob
UnknownLoc unknownLoc;
/// The SPIR-V ModuleOp.
Optional<spirv::ModuleOp> module;
OpBuilder opBuilder;
};
} // namespace
Deserializer::Deserializer(ArrayRef<uint32_t> binary, MLIRContext *context)
: binary(binary), context(context), unknownLoc(UnknownLoc::get(context)),
module(createModuleOp()),
opBuilder(module->getOperation()->getRegion(0)) {}
LogicalResult Deserializer::deserialize() {
if (failed(processHeader()))
return failure();
auto binarySize = binary.size();
curOffset = spirv::kHeaderWordCount;
while (curOffset < binarySize) {
// For each instruction, get its word count from the first word to slice it
// from the stream properly, and then dispatch to the instruction handler.
uint32_t wordCount = binary[curOffset] >> 16;
uint32_t opcode = binary[curOffset] & 0xffff;
if (wordCount == 0)
return context->emitError(unknownLoc, "word count cannot be zero");
uint32_t nextOffset = curOffset + wordCount;
if (nextOffset > binarySize)
return context->emitError(unknownLoc,
"insufficient words for the last instruction");
auto operands = binary.slice(curOffset + 1, wordCount - 1);
if (failed(processInstruction(opcode, operands)))
return failure();
curOffset = nextOffset;
}
return success();
}
Optional<spirv::ModuleOp> Deserializer::collect() { return module; }
LogicalResult Deserializer::processHeader() {
if (binary.size() < spirv::kHeaderWordCount)
return context->emitError(unknownLoc,
"SPIR-V binary module must have a 5-word header");
if (binary[0] != spirv::kMagicNumber)
return context->emitError(unknownLoc, "incorrect magic number");
// TODO(antiagainst): generator number, bound, schema
return success();
}
LogicalResult Deserializer::processInstruction(uint32_t opcode,
ArrayRef<uint32_t> operands) {
switch (opcode) {
case spirv::kOpMemoryModelOpcode:
return processMemoryModel(operands);
default:
break;
}
return context->emitError(unknownLoc, "NYI: opcode ") << opcode;
}
LogicalResult Deserializer::processMemoryModel(ArrayRef<uint32_t> operands) {
if (operands.size() != 2)
return context->emitError(unknownLoc,
"OpMemoryModel must have two operands");
// TODO(antiagainst): use IntegerAttr-backed enum attributes to avoid the
// excessive string conversions here.
auto am = spirv::symbolizeAddressingModel(operands.front());
if (!am)
return context->emitError(unknownLoc,
"unknown addressing model for OpMemoryModel");
auto mm = spirv::symbolizeMemoryModel(operands.back());
if (!mm)
return context->emitError(unknownLoc,
"unknown memory model for OpMemoryModel");
module->setAttr(
"addressing_model",
opBuilder.getStringAttr(spirv::stringifyAddressingModel(*am)));
module->setAttr("memory_model",
opBuilder.getStringAttr(spirv::stringifyMemoryModel(*mm)));
return success();
}
spirv::ModuleOp Deserializer::createModuleOp() {
Builder builder(context);
OperationState state(context, unknownLoc,
spirv::ModuleOp::getOperationName());
// TODO(antiagainst): use target environment to select the version
state.addAttribute("major_version", builder.getI32IntegerAttr(1));
state.addAttribute("minor_version", builder.getI32IntegerAttr(0));
spirv::ModuleOp::build(&builder, &state);
return llvm::cast<spirv::ModuleOp>(Operation::create(state));
}
Optional<spirv::ModuleOp> spirv::deserialize(ArrayRef<uint32_t> binary,
MLIRContext *context) {
Deserializer deserializer(binary, context);
if (failed(deserializer.deserialize()))
return llvm::None;
return deserializer.collect();
}

View File

@@ -0,0 +1,42 @@
//===- SPIRVBinaryUtils.cpp - SPIR-V Binary Module Utils --------*- C++ -*-===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
//
// This file defines common utilities for SPIR-V binary module.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_SPIRV_SERIALIZATION_SPIRV_BINARY_UTILS_H_
#define MLIR_SPIRV_SERIALIZATION_SPIRV_BINARY_UTILS_H_
#include <cstdint>
namespace mlir {
namespace spirv {
/// SPIR-V binary header word count
constexpr unsigned kHeaderWordCount = 5;
/// SPIR-V magic number
constexpr uint32_t kMagicNumber = 0x07230203;
/// Opcode for SPIR-V OpMemoryModel
constexpr uint32_t kOpMemoryModelOpcode = 14;
} // end namespace spirv
} // end namespace mlir
#endif // MLIR_SPIRV_SERIALIZATION_SPIRV_BINARY_UTILS_H_

View File

@@ -0,0 +1,168 @@
//===- Serializer.cpp - MLIR SPIR-V Serialization -------------------------===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
//
// This file defines the MLIR SPIR-V module to SPIR-V binary seralization.
//
//===----------------------------------------------------------------------===//
#include "mlir/SPIRV/Serialization.h"
#include "SPIRVBinaryUtils.h"
#include "mlir/SPIRV/SPIRVOps.h"
#include "mlir/SPIRV/SPIRVTypes.h"
#include "mlir/Support/LogicalResult.h"
#include "llvm/ADT/SmallVector.h"
using namespace mlir;
static inline uint32_t getPrefixedOpcode(uint32_t wordCount, uint32_t opcode) {
assert(((wordCount >> 16) == 0) && "word count out of range!");
return (wordCount << 16) | opcode;
}
namespace {
/// A SPIR-V module serializer.
///
/// A SPIR-V binary module is a single linear stream of instructions; each
/// instruction is composed of 32-bit words with the layout:
///
/// | <word-count>|<opcode> | <operand> | <operand> | ... |
/// | <------ word -------> | <-- word --> | <-- word --> | ... |
///
/// For the first word, the 16 high-order bits are the word count of the
/// instruction, the 16 low-order bits are the opcode enumerant. The
/// instructions then belong to different sections, which must be laid out in
/// the particular order as specified in "2.4 Logical Layout of a Module" of
/// the SPIR-V spec.
class Serializer {
public:
/// Creates a serializer for the given SPIR-V `module`.
explicit Serializer(spirv::ModuleOp module) : module(module) {}
/// Serializes the remembered SPIR-V module.
LogicalResult serialize();
/// Collects the final SPIR-V `binary`.
void collect(SmallVectorImpl<uint32_t> &binary);
private:
/// Creates SPIR-V module header in the given `header`.
void processHeader(SmallVectorImpl<uint32_t> &header);
void processMemoryModel();
private:
/// The SPIR-V module to be serialized.
spirv::ModuleOp module;
/// The next available result <id>.
uint32_t nextID = 0;
// The following are for different SPIR-V instruction sections. They follow
// the logical layout of a SPIR-V module.
SmallVector<uint32_t, 4> capabilities;
SmallVector<uint32_t, 0> extensions;
SmallVector<uint32_t, 0> extendedSets;
SmallVector<uint32_t, 3> memoryModel;
SmallVector<uint32_t, 0> entryPoints;
SmallVector<uint32_t, 4> executionModes;
// TODO(antiagainst): debug instructions
SmallVector<uint32_t, 0> decorations;
SmallVector<uint32_t, 0> typesGlobalValues;
SmallVector<uint32_t, 0> functions;
};
} // namespace
namespace {
#include "mlir/SPIRV/SPIRVSerialization.inc"
}
LogicalResult Serializer::serialize() {
if (failed(module.verify()))
return failure();
// TODO(antiagainst): handle the other sections
processMemoryModel();
return success();
}
void Serializer::collect(SmallVectorImpl<uint32_t> &binary) {
// The number of words in the SPIR-V module header
auto moduleSize = spirv::kHeaderWordCount + capabilities.size() +
extensions.size() + extendedSets.size() +
memoryModel.size() + entryPoints.size() +
executionModes.size() + decorations.size() +
typesGlobalValues.size() + functions.size();
binary.clear();
binary.reserve(moduleSize);
processHeader(binary);
binary.append(capabilities.begin(), capabilities.end());
binary.append(extensions.begin(), extensions.end());
binary.append(extendedSets.begin(), extendedSets.end());
binary.append(memoryModel.begin(), memoryModel.end());
binary.append(entryPoints.begin(), entryPoints.end());
binary.append(executionModes.begin(), executionModes.end());
binary.append(decorations.begin(), decorations.end());
binary.append(typesGlobalValues.begin(), typesGlobalValues.end());
binary.append(functions.begin(), functions.end());
}
void Serializer::processHeader(SmallVectorImpl<uint32_t> &header) {
// The serializer tool ID registered to the Khronos Group
constexpr uint32_t kGeneratorNumber = 22;
// The major and minor version number for the generated SPIR-V binary.
// TODO(antiagainst): use target environment to select the version
constexpr uint8_t kMajorVersion = 1;
constexpr uint8_t kMinorVersion = 0;
header.push_back(spirv::kMagicNumber);
header.push_back((kMajorVersion << 16) | (kMinorVersion << 8));
header.push_back(kGeneratorNumber);
header.push_back(nextID); // ID bound
header.push_back(0); // Schema (reserved word)
}
void Serializer::processMemoryModel() {
// TODO(antiagainst): use IntegerAttr-backed enum attributes to avoid the
// excessive string conversions here.
auto mm = static_cast<uint32_t>(*spirv::symbolizeMemoryModel(
module.getAttrOfType<StringAttr>("memory_model").getValue()));
auto am = static_cast<uint32_t>(*spirv::symbolizeAddressingModel(
module.getAttrOfType<StringAttr>("addressing_model").getValue()));
constexpr uint32_t kNumWords = 3;
memoryModel.reserve(kNumWords);
memoryModel.assign(
{getPrefixedOpcode(kNumWords, spirv::kOpMemoryModelOpcode), am, mm});
}
bool spirv::serialize(spirv::ModuleOp module,
SmallVectorImpl<uint32_t> &binary) {
Serializer serializer(module);
if (failed(serializer.serialize()))
return false;
serializer.collect(binary);
return true;
}

View File

@@ -170,6 +170,10 @@ StringRef tblgen::EnumAttr::getUnderlyingType() const {
return def->getValueAsString("underlyingType");
}
StringRef tblgen::EnumAttr::getUnderlyingToSymbolFnName() const {
return def->getValueAsString("underlyingToSymbolFnName");
}
StringRef tblgen::EnumAttr::getStringToSymbolFnName() const {
return def->getValueAsString("stringToSymbolFnName");
}

View File

@@ -0,0 +1,13 @@
// RUN: mlir-translate -serialize-spirv %s | mlir-translate -deserialize-spirv | FileCheck %s
// CHECK: spv.module {
// CHECK-NEXT: } attributes {addressing_model: "Logical", major_version: 1 : i32, memory_model: "VulkanKHR", minor_version: 0 : i32}
func @spirv_module() -> () {
spv.module {
} attributes {
addressing_model: "Logical",
memory_model: "VulkanKHR"
}
return
}

View File

@@ -11,5 +11,6 @@ add_tablegen(mlir-tblgen MLIR
OpDocGen.cpp
ReferenceImplGen.cpp
RewriterGen.cpp
SPIRVSerializationGen.cpp
)
set_target_properties(mlir-tblgen PROPERTIES FOLDER "Tablegenning")

View File

@@ -109,6 +109,7 @@ static void emitEnumDecl(const Record &enumDef, raw_ostream &os) {
StringRef description = enumAttr.getDescription();
StringRef strToSymFnName = enumAttr.getStringToSymbolFnName();
StringRef symToStrFnName = enumAttr.getSymbolToStringFnName();
StringRef underlyingToSymFnName = enumAttr.getUnderlyingToSymbolFnName();
StringRef maxEnumValFnName = enumAttr.getMaxEnumValFnName();
auto enumerants = enumAttr.getAllCases();
@@ -122,6 +123,9 @@ static void emitEnumDecl(const Record &enumDef, raw_ostream &os) {
emitEnumClass(enumDef, enumName, underlyingType, description, enumerants, os);
// Emit coversion function declarations
os << formatv(
"llvm::Optional<{0}> {1}({2});\n", enumName, underlyingToSymFnName,
underlyingType.empty() ? std::string("unsigned") : underlyingType);
os << formatv("llvm::StringRef {1}({0});\n", enumName, symToStrFnName);
os << formatv("llvm::Optional<{0}> {1}(llvm::StringRef);\n", enumName,
strToSymFnName);
@@ -158,8 +162,10 @@ static void emitEnumDef(const Record &enumDef, raw_ostream &os) {
EnumAttr enumAttr(enumDef);
StringRef enumName = enumAttr.getEnumClassName();
StringRef cppNamespace = enumAttr.getCppNamespace();
std::string underlyingType = enumAttr.getUnderlyingType();
StringRef strToSymFnName = enumAttr.getStringToSymbolFnName();
StringRef symToStrFnName = enumAttr.getSymbolToStringFnName();
StringRef underlyingToSymFnName = enumAttr.getUnderlyingToSymbolFnName();
auto enumerants = enumAttr.getAllCases();
llvm::SmallVector<StringRef, 2> namespaces;
@@ -179,6 +185,21 @@ static void emitEnumDef(const Record &enumDef, raw_ostream &os) {
os << " return \"\";\n";
os << "}\n\n";
os << formatv("llvm::Optional<{0}> {1}({2} value) {{\n", enumName,
underlyingToSymFnName,
underlyingType.empty() ? std::string("unsigned")
: underlyingType)
<< " switch (value) {\n";
for (const auto &enumerant : enumerants) {
auto symbol = enumerant.getSymbol();
auto value = enumerant.getValue();
os << formatv(" case {0}: return {1}::{2};\n", value, enumName,
makeIdentifier(symbol));
}
os << " default: return llvm::None;\n"
<< " }\n"
<< "}\n\n";
os << formatv("llvm::Optional<{0}> {1}(llvm::StringRef str) {{\n", enumName,
strToSymFnName);
os << formatv(" return llvm::StringSwitch<llvm::Optional<{0}>>(str)\n",

View File

@@ -0,0 +1,69 @@
//===- SPIRVSerializationGen.cpp - SPIR-V serialization utility generator -===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
//
// SPIRVSerializationGen generates common utility functions for SPIR-V
// serialization.
//
//===----------------------------------------------------------------------===//
#include "mlir/TableGen/GenInfo.h"
#include "mlir/TableGen/Operator.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"
#include "llvm/TableGen/TableGenBackend.h"
using llvm::formatv;
using llvm::raw_ostream;
using llvm::Record;
using llvm::RecordKeeper;
using mlir::tblgen::Operator;
// Writes the following function to `os`:
// inline uint32_t getOpcode(<op-class-name>) { return <opcode>; }
static void emitGetOpcodeFunction(const llvm::Record &record,
const Operator &op, raw_ostream &os) {
if (llvm::isa<llvm::UnsetInit>(record.getValueInit("opcode")))
return;
os << formatv("inline uint32_t getOpcode({0}) {{ return {1}u; }\n",
op.getQualCppClassName(), record.getValueAsInt("opcode"));
}
static bool emitSerializationUtils(const RecordKeeper &recordKeeper,
raw_ostream &os) {
llvm::emitSourceFileHeader("SPIR-V Serialization Utilities", os);
auto defs = recordKeeper.getAllDerivedDefinitions("SPV_Op");
for (const auto *def : defs) {
Operator op(def);
emitGetOpcodeFunction(*def, op, os);
}
return false;
}
// Registers the enum utility generator to mlir-tblgen.
static mlir::GenRegistration
genEnumDefs("gen-spirv-serial",
"Generate SPIR-V serialization utility definitions",
[](const RecordKeeper &records, raw_ostream &os) {
return emitSerializationUtils(records, os);
});

View File

@@ -4,6 +4,8 @@ set(LIBS
MLIREDSC
MLIRParser
MLIRPass
MLIRSPIRV
MLIRSPIRVSerialization
MLIRStandardOps
MLIRTargetLLVMIR
MLIRTargetNVVMIR