[mlir][spirv] Handle debug information during (de)serialization.

Summary:
This is an initial version, currently supports OpString and OpLine
for autogenerated operations during (de)serialization.

Differential Revision: https://reviews.llvm.org/D79091
This commit is contained in:
Denis Khalikov 2020-05-01 12:51:22 +03:00
parent 7ce389e8ac
commit 29b955f97c
8 changed files with 250 additions and 27 deletions

View File

@ -3079,6 +3079,7 @@ def SPV_OC_OpSourceExtension : I32EnumAttrCase<"OpSourceExtension", 4>
def SPV_OC_OpName : I32EnumAttrCase<"OpName", 5>;
def SPV_OC_OpMemberName : I32EnumAttrCase<"OpMemberName", 6>;
def SPV_OC_OpString : I32EnumAttrCase<"OpString", 7>;
def SPV_OC_OpLine : I32EnumAttrCase<"OpLine", 8>;
def SPV_OC_OpExtension : I32EnumAttrCase<"OpExtension", 10>;
def SPV_OC_OpExtInstImport : I32EnumAttrCase<"OpExtInstImport", 11>;
def SPV_OC_OpExtInst : I32EnumAttrCase<"OpExtInst", 12>;
@ -3204,6 +3205,7 @@ def SPV_OC_OpBranchConditional : I32EnumAttrCase<"OpBranchConditional",
def SPV_OC_OpReturn : I32EnumAttrCase<"OpReturn", 253>;
def SPV_OC_OpReturnValue : I32EnumAttrCase<"OpReturnValue", 254>;
def SPV_OC_OpUnreachable : I32EnumAttrCase<"OpUnreachable", 255>;
def SPV_OC_OpNoLine : I32EnumAttrCase<"OpNoLine", 317>;
def SPV_OC_OpModuleProcessed : I32EnumAttrCase<"OpModuleProcessed", 330>;
def SPV_OC_OpGroupNonUniformElect : I32EnumAttrCase<"OpGroupNonUniformElect", 333>;
def SPV_OC_OpGroupNonUniformBallot : I32EnumAttrCase<"OpGroupNonUniformBallot", 339>;
@ -3223,7 +3225,7 @@ def SPV_OpcodeAttr :
SPV_I32EnumAttr<"Opcode", "valid SPIR-V instructions", [
SPV_OC_OpNop, SPV_OC_OpUndef, SPV_OC_OpSourceContinued, SPV_OC_OpSource,
SPV_OC_OpSourceExtension, SPV_OC_OpName, SPV_OC_OpMemberName, SPV_OC_OpString,
SPV_OC_OpExtension, SPV_OC_OpExtInstImport, SPV_OC_OpExtInst,
SPV_OC_OpLine, SPV_OC_OpExtension, SPV_OC_OpExtInstImport, SPV_OC_OpExtInst,
SPV_OC_OpMemoryModel, SPV_OC_OpEntryPoint, SPV_OC_OpExecutionMode,
SPV_OC_OpCapability, SPV_OC_OpTypeVoid, SPV_OC_OpTypeBool, SPV_OC_OpTypeInt,
SPV_OC_OpTypeFloat, SPV_OC_OpTypeVector, SPV_OC_OpTypeArray,
@ -3262,14 +3264,14 @@ def SPV_OpcodeAttr :
SPV_OC_OpAtomicUMax, SPV_OC_OpAtomicAnd, SPV_OC_OpAtomicOr, SPV_OC_OpAtomicXor,
SPV_OC_OpPhi, SPV_OC_OpLoopMerge, SPV_OC_OpSelectionMerge, SPV_OC_OpLabel,
SPV_OC_OpBranch, SPV_OC_OpBranchConditional, SPV_OC_OpReturn,
SPV_OC_OpReturnValue, SPV_OC_OpUnreachable, SPV_OC_OpModuleProcessed,
SPV_OC_OpGroupNonUniformElect, SPV_OC_OpGroupNonUniformBallot,
SPV_OC_OpGroupNonUniformIAdd, SPV_OC_OpGroupNonUniformFAdd,
SPV_OC_OpGroupNonUniformIMul, SPV_OC_OpGroupNonUniformFMul,
SPV_OC_OpGroupNonUniformSMin, SPV_OC_OpGroupNonUniformUMin,
SPV_OC_OpGroupNonUniformFMin, SPV_OC_OpGroupNonUniformSMax,
SPV_OC_OpGroupNonUniformUMax, SPV_OC_OpGroupNonUniformFMax,
SPV_OC_OpSubgroupBallotKHR
SPV_OC_OpReturnValue, SPV_OC_OpUnreachable, SPV_OC_OpNoLine,
SPV_OC_OpModuleProcessed, SPV_OC_OpGroupNonUniformElect,
SPV_OC_OpGroupNonUniformBallot, SPV_OC_OpGroupNonUniformIAdd,
SPV_OC_OpGroupNonUniformFAdd, SPV_OC_OpGroupNonUniformIMul,
SPV_OC_OpGroupNonUniformFMul, SPV_OC_OpGroupNonUniformSMin,
SPV_OC_OpGroupNonUniformUMin, SPV_OC_OpGroupNonUniformFMin,
SPV_OC_OpGroupNonUniformSMax, SPV_OC_OpGroupNonUniformUMax,
SPV_OC_OpGroupNonUniformFMax, SPV_OC_OpSubgroupBallotKHR
]>;
// End opcode section. Generated from SPIR-V spec; DO NOT MODIFY!

View File

@ -26,7 +26,8 @@ class ModuleOp;
/// Serializes the given SPIR-V `module` and writes to `binary`. On failure,
/// reports errors to the error handler registered with the MLIR context for
/// `module`.
LogicalResult serialize(ModuleOp module, SmallVectorImpl<uint32_t> &binary);
LogicalResult serialize(ModuleOp module, SmallVectorImpl<uint32_t> &binary,
bool emitDebugInfo = false);
/// Deserializes the given SPIR-V `binary` module and creates a MLIR ModuleOp
/// in the given `context`. Returns the ModuleOp on success; otherwise, reports

View File

@ -68,6 +68,16 @@ struct BlockMergeInfo {
: mergeBlock(m), continueBlock(c) {}
};
/// A struct for containing OpLine instruction information.
struct DebugLine {
uint32_t fileID;
uint32_t line;
uint32_t col;
DebugLine(uint32_t fileIDNum, uint32_t lineNum, uint32_t colNum)
: fileID(fileIDNum), line(lineNum), col(colNum) {}
};
/// Map from a selection/loop's header block to its merge (and continue) target.
using BlockMergeInfoMap = DenseMap<Block *, BlockMergeInfo>;
@ -232,6 +242,23 @@ private:
/// Processes a SPIR-V OpConstantNull instruction with the given `operands`.
LogicalResult processConstantNull(ArrayRef<uint32_t> operands);
//===--------------------------------------------------------------------===//
// Debug
//===--------------------------------------------------------------------===//
/// Discontinues any source-level location information that might be active
/// from a previous OpLine instruction.
LogicalResult clearDebugLine();
/// Creates a FileLineColLoc with the OpLine location information.
Location createFileLineColLoc(OpBuilder opBuilder);
/// Processes a SPIR-V OpLine instruction with the given `operands`.
LogicalResult processDebugLine(ArrayRef<uint32_t> operands);
/// Processes a SPIR-V OpString instruction with the given `operands`.
LogicalResult processDebugString(ArrayRef<uint32_t> operands);
//===--------------------------------------------------------------------===//
// Control flow
//===--------------------------------------------------------------------===//
@ -376,6 +403,10 @@ private:
/// The SPIR-V binary module.
ArrayRef<uint32_t> binary;
/// Contains the data of the OpLine instruction which precedes the current
/// processing instruction.
llvm::Optional<DebugLine> debugLine;
/// The current word offset into the binary module.
unsigned curOffset = 0;
@ -444,6 +475,9 @@ private:
// Result <id> to name mapping.
DenseMap<uint32_t, StringRef> nameMap;
// Result <id> to debug info mapping.
DenseMap<uint32_t, StringRef> debugInfoMap;
// Result <id> to decorations mapping.
DenseMap<uint32_t, MutableDictionaryAttr> decorations;
@ -1506,6 +1540,7 @@ LogicalResult Deserializer::processBranch(ArrayRef<uint32_t> operands) {
auto *target = getOrCreateBlock(operands[0]);
opBuilder.create<spirv::BranchOp>(unknownLoc, target);
clearDebugLine();
return success();
}
@ -1536,6 +1571,7 @@ Deserializer::processBranchConditional(ArrayRef<uint32_t> operands) {
/*trueArguments=*/ArrayRef<Value>(), falseBlock,
/*falseArguments=*/ArrayRef<Value>(), weights);
clearDebugLine();
return success();
}
@ -1994,6 +2030,57 @@ LogicalResult Deserializer::structurizeControlFlow() {
return success();
}
//===----------------------------------------------------------------------===//
// Debug
//===----------------------------------------------------------------------===//
Location Deserializer::createFileLineColLoc(OpBuilder opBuilder) {
if (!debugLine)
return unknownLoc;
auto fileName = debugInfoMap.lookup(debugLine->fileID).str();
if (fileName.empty())
fileName = "<unknown>";
return opBuilder.getFileLineColLoc(opBuilder.getIdentifier(fileName),
debugLine->line, debugLine->col);
}
LogicalResult Deserializer::processDebugLine(ArrayRef<uint32_t> operands) {
// According to SPIR-V spec:
// "This location information applies to the instructions physically
// following this instruction, up to the first occurrence of any of the
// following: the next end of block, the next OpLine instruction, or the next
// OpNoLine instruction."
if (operands.size() != 3)
return emitError(unknownLoc, "OpLine must have 3 operands");
debugLine = DebugLine(operands[0], operands[1], operands[2]);
return success();
}
LogicalResult Deserializer::clearDebugLine() {
debugLine = llvm::None;
return success();
}
LogicalResult Deserializer::processDebugString(ArrayRef<uint32_t> operands) {
if (operands.size() < 2)
return emitError(unknownLoc, "OpString needs at least 2 operands");
if (!debugInfoMap.lookup(operands[0]).empty())
return emitError(unknownLoc,
"duplicate debug string found for result <id> ")
<< operands[0];
unsigned wordIndex = 1;
StringRef debugString = decodeStringLiteral(operands, wordIndex);
if (wordIndex != operands.size())
return emitError(unknownLoc,
"unexpected trailing words in OpString instruction");
debugInfoMap[operands[0]] = debugString;
return success();
}
//===----------------------------------------------------------------------===//
// Instruction
//===----------------------------------------------------------------------===//
@ -2085,10 +2172,15 @@ LogicalResult Deserializer::processInstruction(spirv::Opcode opcode,
return processGlobalVariable(operands);
}
break;
case spirv::Opcode::OpLine:
return processDebugLine(operands);
case spirv::Opcode::OpNoLine:
return clearDebugLine();
case spirv::Opcode::OpName:
return processName(operands);
case spirv::Opcode::OpModuleProcessed:
case spirv::Opcode::OpString:
return processDebugString(operands);
case spirv::Opcode::OpModuleProcessed:
case spirv::Opcode::OpSource:
case spirv::Opcode::OpSourceContinued:
case spirv::Opcode::OpSourceExtension:

View File

@ -41,7 +41,7 @@ static LogicalResult encodeInstructionInto(SmallVectorImpl<uint32_t> &binary,
ArrayRef<uint32_t> operands) {
uint32_t wordCount = 1 + operands.size();
binary.push_back(spirv::getPrefixedOpcode(wordCount, op));
binary.append(operands.begin(), operands.end());
binary.append(operands.begin(), operands.end());
return success();
}
@ -132,7 +132,7 @@ namespace {
class Serializer {
public:
/// Creates a serializer for the given SPIR-V `module`.
explicit Serializer(spirv::ModuleOp module);
explicit Serializer(spirv::ModuleOp module, bool emitDebugInfo = false);
/// Serializes the remembered SPIR-V module.
LogicalResult serialize();
@ -189,6 +189,8 @@ private:
void processCapability();
void processDebugInfo();
void processExtension();
void processMemoryModel();
@ -375,6 +377,10 @@ private:
LogicalResult emitDecoration(uint32_t target, spirv::Decoration decoration,
ArrayRef<uint32_t> params = {});
/// Emits an OpLine instruction with the given `loc` location information into
/// the given `binary` vector.
LogicalResult emitDebugLine(SmallVectorImpl<uint32_t> &binary, Location loc);
private:
/// The SPIR-V module to be serialized.
spirv::ModuleOp module;
@ -382,6 +388,13 @@ private:
/// An MLIR builder for getting MLIR constructs.
mlir::Builder mlirBuilder;
/// A flag which indicates if the debuginfo should be emitted.
bool emitDebugInfo = false;
/// The <id> of the OpString instruction, which specifies a file name, for
/// use by other debug instructions.
uint32_t fileID = 0;
/// The next available result <id>.
uint32_t nextID = 1;
@ -394,7 +407,7 @@ private:
SmallVector<uint32_t, 3> memoryModel;
SmallVector<uint32_t, 0> entryPoints;
SmallVector<uint32_t, 4> executionModes;
// TODO(antiagainst): debug instructions
SmallVector<uint32_t, 0> debug;
SmallVector<uint32_t, 0> names;
SmallVector<uint32_t, 0> decorations;
SmallVector<uint32_t, 0> typesGlobalValues;
@ -482,8 +495,9 @@ private:
};
} // namespace
Serializer::Serializer(spirv::ModuleOp module)
: module(module), mlirBuilder(module.getContext()) {}
Serializer::Serializer(spirv::ModuleOp module, bool emitDebugInfo)
: module(module), mlirBuilder(module.getContext()),
emitDebugInfo(emitDebugInfo) {}
LogicalResult Serializer::serialize() {
LLVM_DEBUG(llvm::dbgs() << "+++ starting serialization +++\n");
@ -495,6 +509,7 @@ LogicalResult Serializer::serialize() {
processCapability();
processExtension();
processMemoryModel();
processDebugInfo();
// Iterate over the module body to serialize it. Assumptions are that there is
// only one basic block in the moduleOp
@ -525,6 +540,7 @@ void Serializer::collect(SmallVectorImpl<uint32_t> &binary) {
binary.append(memoryModel.begin(), memoryModel.end());
binary.append(entryPoints.begin(), entryPoints.end());
binary.append(executionModes.begin(), executionModes.end());
binary.append(debug.begin(), debug.end());
binary.append(names.begin(), names.end());
binary.append(decorations.begin(), decorations.end());
binary.append(typesGlobalValues.begin(), typesGlobalValues.end());
@ -569,6 +585,19 @@ void Serializer::processCapability() {
{static_cast<uint32_t>(cap)});
}
void Serializer::processDebugInfo() {
if (!emitDebugInfo)
return;
auto fileLoc = module.getLoc().dyn_cast<FileLineColLoc>();
auto fileName = fileLoc ? fileLoc.getFilename() : "<unknown>";
fileID = getNextID();
SmallVector<uint32_t, 16> operands;
operands.push_back(fileID);
spirv::encodeStringLiteralInto(operands, fileName);
encodeInstructionInto(debug, spirv::Opcode::OpString, operands);
// TODO: Encode more debug instructions.
}
void Serializer::processExtension() {
llvm::SmallVector<uint32_t, 16> extName;
for (spirv::Extension ext : module.vce_triple()->getExtensions()) {
@ -1838,13 +1867,26 @@ LogicalResult Serializer::emitDecoration(uint32_t target,
return success();
}
LogicalResult Serializer::emitDebugLine(SmallVectorImpl<uint32_t> &binary,
Location loc) {
if (!emitDebugInfo)
return success();
auto fileLoc = loc.dyn_cast<FileLineColLoc>();
if (fileLoc)
encodeInstructionInto(binary, spirv::Opcode::OpLine,
{fileID, fileLoc.getLine(), fileLoc.getColumn()});
return success();
}
LogicalResult spirv::serialize(spirv::ModuleOp module,
SmallVectorImpl<uint32_t> &binary) {
SmallVectorImpl<uint32_t> &binary,
bool emitDebugInfo) {
if (!module.vce_triple().hasValue())
return module.emitError(
"module must have 'vce_triple' attribute to be serializeable");
Serializer serializer(module);
Serializer serializer(module, emitDebugInfo);
if (failed(serializer.serialize()))
return failure();

View File

@ -91,7 +91,8 @@ static LogicalResult serializeModule(ModuleOp module, raw_ostream &output) {
if (spirvModules.size() != 1)
return module.emitError("found more than one 'spv.module' op");
if (failed(spirv::serialize(spirvModules[0], binary)))
if (failed(
spirv::serialize(spirvModules[0], binary, /*emitDebuginfo=*/false)))
return failure();
output.write(reinterpret_cast<char *>(binary.data()),
@ -114,7 +115,7 @@ void registerToSPIRVTranslation() {
//===----------------------------------------------------------------------===//
static LogicalResult roundTripModule(llvm::SourceMgr &sourceMgr,
raw_ostream &output,
bool emitDebugInfo, raw_ostream &output,
MLIRContext *context) {
// Parse an MLIR module from the source manager.
auto srcModule = OwningModuleRef(parseSourceFile(sourceMgr, context));
@ -131,7 +132,7 @@ static LogicalResult roundTripModule(llvm::SourceMgr &sourceMgr,
if (std::next(spirvModules.begin()) != spirvModules.end())
return srcModule->emitError("found more than one 'spv.module' op");
if (failed(spirv::serialize(*spirvModules.begin(), binary)))
if (failed(spirv::serialize(*spirvModules.begin(), binary, emitDebugInfo)))
return failure();
// Then deserialize to get back a SPIR-V module.
@ -153,7 +154,18 @@ void registerTestRoundtripSPIRV() {
TranslateRegistration roundtrip(
"test-spirv-roundtrip", [](llvm::SourceMgr &sourceMgr,
raw_ostream &output, MLIRContext *context) {
return roundTripModule(sourceMgr, output, context);
return roundTripModule(sourceMgr, /*emitDebugInfo=*/false, output,
context);
});
}
void registerTestRoundtripDebugSPIRV() {
TranslateRegistration roundtrip(
"test-spirv-roundtrip-debug",
[](llvm::SourceMgr &sourceMgr, raw_ostream &output,
MLIRContext *context) {
return roundTripModule(sourceMgr, /*emitDebugInfo=*/true, output,
context);
});
}
} // namespace mlir

View File

@ -0,0 +1,60 @@
// RUN: mlir-translate -test-spirv-roundtrip-debug -mlir-print-debuginfo %s | FileCheck %s
spv.module Logical GLSL450 requires #spv.vce<v1.0, [Shader], []> {
spv.func @arithmetic(%arg0 : vector<4xf32>, %arg1 : vector<4xf32>) "None" {
// CHECK: loc({{".*debug.mlir"}}:6:10)
%0 = spv.FAdd %arg0, %arg1 : vector<4xf32>
// CHECK: loc({{".*debug.mlir"}}:8:10)
%1 = spv.FNegate %arg0 : vector<4xf32>
spv.Return
}
spv.func @atomic(%ptr: !spv.ptr<i32, Workgroup>, %value: i32, %comparator: i32) "None" {
// CHECK: loc({{".*debug.mlir"}}:14:10)
%1 = spv.AtomicAnd "Device" "None" %ptr, %value : !spv.ptr<i32, Workgroup>
spv.Return
}
spv.func @bitwiser(%arg0 : i32, %arg1 : i32) "None" {
// CHECK: loc({{".*debug.mlir"}}:20:10)
%0 = spv.BitwiseAnd %arg0, %arg1 : i32
spv.Return
}
spv.func @convert(%arg0 : f32) "None" {
// CHECK: loc({{".*debug.mlir"}}:26:10)
%0 = spv.ConvertFToU %arg0 : f32 to i32
spv.Return
}
spv.func @composite(%arg0 : !spv.struct<f32, !spv.struct<!spv.array<4xf32>, f32>>, %arg1: !spv.array<4xf32>, %arg2 : f32, %arg3 : f32) "None" {
// CHECK: loc({{".*debug.mlir"}}:32:10)
%0 = spv.CompositeInsert %arg1, %arg0[1 : i32, 0 : i32] : !spv.array<4xf32> into !spv.struct<f32, !spv.struct<!spv.array<4xf32>, f32>>
// CHECK: loc({{".*debug.mlir"}}:34:10)
%1 = spv.CompositeConstruct %arg2, %arg3 : vector<2xf32>
spv.Return
}
spv.func @group_non_uniform(%val: f32) "None" {
// CHECK: loc({{".*debug.mlir"}}:40:10)
%0 = spv.GroupNonUniformFAdd "Workgroup" "Reduce" %val : f32
spv.Return
}
spv.func @logical(%arg0: i32, %arg1: i32) "None" {
// CHECK: loc({{".*debug.mlir"}}:46:10)
%0 = spv.IEqual %arg0, %arg1 : i32
spv.Return
}
spv.func @memory_accesses(%arg0 : !spv.ptr<!spv.array<4x!spv.array<4xf32>>, StorageBuffer>, %arg1 : i32, %arg2 : i32) "None" {
// CHECK: loc({{".*debug.mlir"}}:52:10)
%2 = spv.AccessChain %arg0[%arg1, %arg2] : !spv.ptr<!spv.array<4x!spv.array<4xf32>>, StorageBuffer>
// CHECK: loc({{".*debug.mlir"}}:54:10)
%3 = spv.Load "StorageBuffer" %2 : f32
// CHECK: loc({{.*debug.mlir"}}:56:5)
spv.Store "StorageBuffer" %2, %3 : f32
// CHECK: loc({{".*debug.mlir"}}:58:5)
spv.Return
}
}

View File

@ -660,6 +660,8 @@ static void emitSerializationFunction(const Record *attrClass,
opVar, record->getValueAsString("extendedInstSetName"),
record->getValueAsInt("extendedInstOpcode"), operands);
} else {
// Emit debug info.
os << formatv(" emitDebugLine(functionBody, {0}.getLoc());\n", opVar);
os << formatv(" encodeInstructionInto("
"functionBody, spirv::getOpcode<{0}>(), {1});\n",
op.getQualCppClassName(), operands);
@ -900,14 +902,22 @@ static void emitDeserializationFunction(const Record *attrClass,
emitOperandDeserialization(op, record->getLoc(), " ", words, wordIndex,
operands, attributes, os);
os << formatv(
" auto {1} = opBuilder.create<{0}>(unknownLoc, {2}, {3}, {4}); "
"(void){1};\n",
op.getQualCppClassName(), opVar, resultTypes, operands, attributes);
os << formatv(" Location loc = createFileLineColLoc(opBuilder);\n");
os << formatv(" auto {1} = opBuilder.create<{0}>(loc, {2}, {3}, {4}); "
"(void){1};\n",
op.getQualCppClassName(), opVar, resultTypes, operands,
attributes);
if (op.getNumResults() == 1) {
os << formatv(" valueMap[{0}] = {1}.getResult();\n\n", valueID, opVar);
}
// According to SPIR-V spec:
// This location information applies to the instructions physically following
// this instruction, up to the first occurrence of any of the following: the
// next end of block.
os << formatv(" if ({0}.hasTrait<OpTrait::IsTerminator>())\n", opVar);
os << formatv(" clearDebugLine();\n");
// Decorations
emitDecorationDeserialization(op, " ", valueID, attributes, os);
os << " return success();\n";

View File

@ -50,9 +50,13 @@ static llvm::cl::opt<bool> verifyDiagnostics(
namespace mlir {
// Defined in the test directory, no public header.
void registerTestRoundtripSPIRV();
void registerTestRoundtripDebugSPIRV();
} // namespace mlir
static void registerTestTranslations() { registerTestRoundtripSPIRV(); }
static void registerTestTranslations() {
registerTestRoundtripSPIRV();
registerTestRoundtripDebugSPIRV();
}
int main(int argc, char **argv) {
registerAllDialects();